The Handler class is the entrance point for our project because AWS Lambda runtime calls it first. You already know that AWS will invoke the handleRequest method for each invocation of Lambda function. To create the object, Lambda runtime uses the default constructor of the class where the Handler is. So, to inject dependencies to our class, we will use this knowledge and initiate the injection when the default constructor is called. Moreover, Lambda runtime will keep a cached copy of an object once it is initiated and shared among different invocations of your Lambda function. It means that the injection will not happen for every invocation and, although we cannot predict when a new instance of Handler class will be created, will be cached.
We can start adding a Guice injector as a static field in the Handler class:
public class Handler extends LambdaHandler<AuthorizationInput, AuthorizationOutput> { private static final Injector INJECTOR = Guice.createInjector(new
DependencyInjectionModule()); @Override public AuthorizationOutput handleRequest(AuthorizationInput input, Context context) { ..... }
}
Now we can add a UserService field to our Handler class and its setter:
private UserService userService; @Inject public void setUserService(UserService userService) { this.userService = userService; }
Here, note the @Inject annotation usage. It is a JSR-330 annotation, which indicates that UserService is needed as a dependency in the Handler class. But putting this annotation alone does not trigger its execution, so we must tell Guice to evaluate the javax.inject annotations and provide the dependencies. In order to achieve this result, we must create a default constructor for our Handler() method and add the following directive there:
public Handler() { INJECTOR.injectMembers(this);
Objects.requireNonNull(userService); }
Note that we added the Objects.requireNonNull check just after the injectMembers call. This ensures that the dependency is injected. At this step, even before we deploy the code to AWS Runtime, we can write a Junit test and see that DI is working. Let us see how to do that:
- Let's create the test file:
$ touch lambda-authorizer/src/test/java/com/serverlessbook/
lambda/authorizer/HandlerTest.java
- We will write the test now:
package com.serverlessbook.lambda.authorizer; import org.junit.Test; public class HandlerTest { @Test public void testDependencies() throws Exception { Handler testHandler = new Handler(); } }
- Let's execute the test:
$ ./gradlew test
We see that it passes. You can play with the handler code, for example, removing the @Inject annotation and seeing that the test fails.
Now, given that we have UserService injected into our Handler, we can finally modify the handleRequest method to consume this service. This method will receive the authorization token input from API Gateway, authorize it against the service and return the result:
@Override public AuthorizationOutput handleRequest(AuthorizationInput input, Context context) { final String authenticationToken = input.getAuthorizationToken(); final PolicyDocument policyDocument = new PolicyDocument(); PolicyStatement.Effect policyEffect = PolicyStatement.Effect.ALLOW; String principalId = null; try { User authenticatedUser = userService.getUserByToken(authenticationToken); principalId = String.valueOf(authenticatedUser.getId()); } catch (UserNotFoundException userNotFoundException) { policyEffect = PolicyStatement.Effect.DENY; } policyDocument.withPolicyStatement(new PolicyStatement("execute-api:Invoke", policyEffect, input.getMethodArn())); return new AuthorizationOutput(principalId, policyDocument); }