Soklet Logo

Core Concepts

Instance Creation

Soklet must create and provide instances of types that contain Resource Methods on your behalf. To do this, it delegates to the InstanceProvider::provide(Class<?>) method of your configured InstanceProvider.

// Soklet will ask its InstanceProvider to instantiate ExampleResource
public class ExampleResource {
  @GET("/")
  public String index() {
    return "Hello, world!";
  }
}

Further, if Soklet encounters a Resource Method Parameter that it does not know how to inject - one of your application-specific types, for example - it will ask for an instance of it via InstanceProvider::provide(Parameter):

@POST("/lottery")
public boolean playLottery(
  @QueryParameter List<String> numbers,
  LotteryBackend lotteryBackend // vended by your InstanceProvider
) {
  return lotteryBackend.isWinner(numbers);
}

For a list of all Resource Method Parameter types that Soklet will inject by default, see Default Injections.

Why is this useful?

By delegating instance provisioning to code you can control, Soklet gives you the ability to construct arbitrarily complex objects. This is in contrast to frameworks which require your types to be warped to conform to instantiation rules, e.g. "must have a default constructor" or "everything is static".

Further, if you use a Dependency Injection library, it's easy to plug it in, making Soklet use the same plumbing the rest of your app already does. The simplicity of a consistent app-wide instantiation approach together with the effectiveness of modern DI tooling is a powerful combination.


Specifying an Instance Provider

Soklet applications use InstanceProvider::defaultInstance out-of-the-box, which can instantiate any type that has a default (no-argument) constructor.

Should you need more flexibility, here's how to supply your own InstanceProvider:

SokletConfig config = SokletConfig.withServer(
  Server.withPort(8080).build()
).instanceProvider(new InstanceProvider() {
  @Nonnull
  @Override  
  public <T> T provide(@Nonnull Class<T> instanceClass) {
    // Use vanilla JDK reflection and create a new instance every time.
    // More advanced implementations might cache off instances,
    // specially instantiate certain types, etc.
    try {        
      return instanceClass.getDeclaredConstructor().newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  // Provides instances for a Resource Method parameters
  @Nonnull
  @Override
  @SuppressWarnings("unchecked")
  public <T> T provide(@Nonnull Parameter parameter) {
    // Just delegate to provide(Class<T>) above
    Class<T> instanceClass = (Class<T>) parameter.getType();
    return provide(instanceClass);
  }
}).build();

References:

Solving the "Robot Legs" Problem

It can be useful to configure your InstanceProvider to know the difference between "flavors" of the same type.

This is useful for solving the Robot Legs Problem, where you might examine a qualifying annotation on a parameter to disambiguate vended instances.

For example, given this Resource Method that accepts 2 LegFactory instances...

// Define some custom annotations...
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Left {}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Right {}

// ...and use them in this Resource Method:
@GET("/robot-generator")
public Robot robotGenerator(
  @Left LegFactory leftLegFactory,
  @Right LegFactory rightLegFactory,
  BodyFactory bodyFactory
) {
  // Build a robot
  LeftLeg leftLeg = leftLegFactory.buildLeg();
  LeftLeg rightLeg = rightLegFactory.buildLeg();
  Body body = bodyFactory.build();
  return new Robot(leftLeg, rightLeg, body);
}

...your InstanceProvider::provide(Parameter) implementation might look like this:

@Nonnull
@Override
@SuppressWarnings("unchecked")
public <T> T provide(@Nonnull Parameter parameter) {
  Class<T> type = (Class<T>) parameter.getType();

  // Pick the appropriate LegFactory by inspecting annotations
  if (type == LegFactory.class) {
    if (parameter.isAnnotationPresent(Left.class))
      return (T) LEFT_INSTANCE;
    if (parameter.isAnnotationPresent(Right.class))
      return (T) RIGHT_INSTANCE;

    throw new IllegalArgumentException("LegFactory requires @Left or @Right");
  }

  // No qualifier logic needed - use the class-based path
  return provide(type);
}

Dependency Injection

In practice, you will likely want to tie in to whatever Dependency Injection library your application uses and have the DI infrastructure vend your instances.

Here's how it might look if you use Google Guice:

// Standard Guice setup
Injector injector = Guice.createInjector(new MyExampleAppModule());

SokletConfig config = SokletConfig.withServer(
  Server.withPort(8080).build()
).instanceProvider(new InstanceProvider() {
  @Nonnull
  @Override  
  public <T> T provide(@Nonnull Class<T> instanceClass) {
    // Have Soklet ask the Guice Injector for the instance
    return injector.getInstance(instanceClass);     
  }
}).build();

Now, your Resources are dependency-injected just like the rest of your application is:

public class WidgetResource {
  private WidgetService widgetService;

  // JSR 330 @Inject annotation used by Guice
  @Inject
  public WidgetResource(WidgetService widgetService) {
    this.widgetService = widgetService;
  }

  @GET("/widgets")
  public List<Widget> widgets() {
    return widgetService.findWidgets();
  }
}

Default Injections

The following injections do not consult your InstanceProvider because Soklet supports them out-of-the-box.

Soklet will automatically inject Resource Method Parameters which are decorated with the following annotations:

Soklet will automatically inject Resource Method Parameters if they are of the following types:

Instances any other type of parameter will be requested from your InstanceProvider.

Previous
Request Lifecycle