• Home
  • Testimonials
  • Blog
  • Contact Us

At a Glance of a Key

Crafting Dreams into Ventures, Code into Excellence: Your Journey to Success

  • Home
  • Testimonials
  • Blog
  • Contact Us

Can I inject this class ?

2021-06-12 Development Tips & Tricks No Comments 4 minute read

One of the biggest pain points using injection in Java is that if you missed something, you will find it during runtime.
Many code commits were made where everything passed the code review, and looked legit, just to find out that it doesn’t work due to a missing injection.

Let me share a small example to show how it looks like. Let’s say we have one Guice module, which provider AWS credentials objects and a DDB client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyModule extends AbstractModule {

    private final Regions region;
    public MyModule(final Regions region) {
        this.region = region;
    }

    @Override
    protected void configure() {
        bind(Regions.class)
                .annotatedWith(named("region"))
                .toInstance(this.region);
    }

    @Provides
    @Singleton
    AWSCredentialsProvider getCredentialsProvider() {
        return DefaultAWSCredentialsProviderChain.getInstance();
    }

    @Provides
    @Singleton
    AmazonDynamoDB getDynamoDB(final AWSCredentialsProvider credentialsProvider) {
        return AmazonDynamoDBClientBuilder.standard()
                .withCredentials(credentialsProvider)
                .withRegion(region)
                .build();
    }
}

Then, somewhere we have a class that is using DDB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Singleton
public class TableSizeProvider {

    private final AmazonDynamoDB ddbClient;

    @Inject
    public TableSizeProvider(final AmazonDynamoDB ddbClient) {
        this.ddbClient = ddbClient;
    }

    public long getTableSizeBytes(final String tableName) {
        return ddbClient.describeTable(tableName)
                .getTable()
                .getTableSizeBytes();
    }
}

The class is injected using Guice, and everyone is happy.
Then, a new feature was requested, to notify the table size via SQS every 60 seconds.
As good engineers, we approached the problem, and added a new scheduled thread that will get the table size and send it to the requested queue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Singleton
public class TableSizePeriodicReporter {

    // In real life, this would sit in a configuration file.
    private final static String QUEUE_NAME = "TableSizeMonitorQueue";
    private final static Duration TIME_BETWEEN_CHECKS = Duration.ofMinutes(1);
    private final static Set<string> TABLES_TO_MONITOR = ImmutableSet.of("Jobs", "Clients");

    private final TableSizeProvider tableSizeProvider;
    private final AmazonSQS sqsClient;
    private final ScheduledExecutorService scheduledWorker;
    private String queueUrl;

    private final AtomicBoolean started = new AtomicBoolean(false);

    @Inject
    public TableSizePeriodicReporter(final TableSizeProvider tableSizeProvider,
                                     final AmazonSQS sqsClient) {

        this.tableSizeProvider = tableSizeProvider;
        this.sqsClient = sqsClient;
        this.scheduledWorker = Executors.newSingleThreadScheduledExecutor();
    }

    public void start() {
        if (!started.getAndSet(true)) {
            this.queueUrl = sqsClient.getQueueUrl(QUEUE_NAME).getQueueUrl();
            this.scheduledWorker.execute(this::reportSize);
        }
    }

    public void shutdown() {
        this.scheduledWorker.shutdown();
    }

    private void reportSize() {

        // Go over all the tables, get their size and send in a message
        TABLES_TO_MONITOR.forEach(tableName -> {
            final long tableSize = tableSizeProvider.getTableSizeBytes(tableName);
            final String message = String.format("Table %s size is %d", tableName, tableSize);

            // We know that queueUrl is set as this method is triggered only internally from this class
            sqsClient.sendMessage(queueUrl, message);
        });

        // Schedule the next execution
        scheduledWorker.schedule(this::reportSize, TIME_BETWEEN_CHECKS.toMillis(), TimeUnit.MILLISECONDS);
    }
}

And it was added also to our Main class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Main {

    @SneakyThrows(InterruptedException.class)
    public static void main(final String[] args) {

        final Regions region = getRegionOrDie(args);
        final Injector injector = createInjector(
                new MyModule(region)
        );

        final TableSizePeriodicReporter tableSizeReporter = injector.getInstance(TableSizePeriodicReporter.class);
        tableSizeReporter.start();

        // Oops, looks like we have a dead-lock that keeps the program running.
        Thread.currentThread().join();
    }

    private static Regions getRegionOrDie(final String[] args) {

        try {
            return Regions.fromName(args[0]);

        } catch(final Exception e) {
            System.err.printf("Could not parse region from arguments: %s%n", args[0]);
            System.exit(1);

            // Make our compiler happy.
            return null;
        }
    }
}

You are happy that you finished your new feature and go to test it. Compilation… Passed! That’s a good sign, no?
You are starting your service and getting an error stating that AmazonSQS class cannot be injected. whoops.
This is a simple example, but think that you’ve just added a new piece to an enterprise software that broke some things, and was not that easy discoverable. Doesn’t sounds fun right?

The solution is to simply add a new @Provides implementation to MyModule which will tell Guice “how” to inject this class. Something similar to this:

1
2
3
4
5
6
7
8
@Provides
@Singleton
AmazonSQS getAmazonSQS(final AWSCredentialsProvider credentialsProvider) {
    return AmazonSQSClientBuilder.standard()
            .withCredentials(credentialsProvider)
            .withRegion(region)
            .build();
}

But could you find it earlier, and not during runtime when testing your changes ? I say YES!

There is no way to know whenever a class will be injected successfully, without going and injecting it. So, what if you could write a unit test that will try to inject the class ? Sounds easy enough… let’s go and do it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InjectionTest {

    private final static Regions TEST_REGION = Regions.US_EAST_1;
    private static Injector injector;

    @BeforeAll
    public static void setup() {
        injector = createInjector(new MyModule(TEST_REGION));
    }

    @Test
    public void testTableSizePeriodicReporterInjection() {

        final TableSizePeriodicReporter reporter = injector.getInstance(TableSizePeriodicReporter.class);
        assertNotNull(reporter);
    }
}

We got what we wanted – If you will run this unit test, and I suppose you are running your unit tests as part of your build process, you’ll get a failure stating that this class cannot be injected. But can we do it better ?

What if we could do that for all the classes that we care about ? Let’s leverage JUnit5 parameterized tests feature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class InjectionTest {

    private final static Regions TEST_REGION = Regions.US_EAST_1;
    private static Injector injector;

    @BeforeAll
    public static void setup() {
        injector = createInjector(new MyModule(TEST_REGION));
    }

    @ParameterizedTest
    @MethodSource("getClassesToInject")
    public void testClassInjection(final Class<?> type) {

        final Object obj = injector.getInstance(type);
        assertNotNull(obj);
    }

    private static Stream<arguments> getClassesToInject() {

        return Stream.of(
                Arguments.of(TableSizeProvider.class),
                Arguments.of(TableSizePeriodicReporter.class)
        );
    }
}

Now, we can provide a list of classes to test for injection and reduce the chances of having injection problems during Runtime.
Yet, I think we can do better. This solution is neat, but still requires you and other team members to go and explicitly add your classes into the test. Luckily for us, we have Reflection, so we can define the list of namespaces to cover, and the test will check all the classes within automatically.

A suggested unit test may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class InjectionTest {

    private final static Set<string> PACKAGES = ImmutableSet.of(
            "org.sirotin.example"
    );

    private final static Regions TEST_REGION = Regions.US_EAST_1;
    private static Injector injector;

    @BeforeAll
    public static void setup() {
        injector = createInjector(new MyModule(TEST_REGION));
    }

    @ParameterizedTest
    @MethodSource("getClassesToInject")
    public void testClassInjection(final Class<?> type) {

        final Object obj = injector.getInstance(type);
        assertNotNull(obj);
    }

    private static Stream<arguments> getClassesToInject() {

        final List<arguments> result = new ArrayList<>();
        PACKAGES.forEach(packageName -> result.addAll(getClassesForPackage(packageName)));
        return result.stream();
    }

    private static List<arguments> getClassesForPackage(final String packageName) {

        final Reflections reflections = new Reflections(
                new ConfigurationBuilder()
                        .setUrls(ClasspathHelper.forPackage(packageName))
                        .setScanners(new SubTypesScanner(false))
        );

        final Set<class<?>> types = reflections.getSubTypesOf(Object.class);
        return types.stream()
                .filter(InjectionTest::isAnnotatedWithInject)
                .map(Arguments::of)
                .collect(Collectors.toList());
    }

    private static boolean isAnnotatedWithInject(final Class<?> clazz) {

        for(final Constructor<?> constructor : clazz.getConstructors()) {
            if (constructor.isAnnotationPresent(Inject.class)) {
                return true;
            }
        }
        return false;
    }
}

Reflections will find all the classes within the provided list of PACKAGES, and then for each one will try to find a constructor with the @Inject annotation. It will collect all its findings, and pass them as a stream of arguments to the testClassInjection method, that will try to inject them.

The full source code of this example can be found here.

– Alexander

Oh hi there 👋
It’s nice to meet you.

Sign up to receive a notification when new posts are published!

We don’t spam!

Check your inbox or spam folder to confirm your subscription.

GuiceJavaTestingUnit Tests

Is it really a Singleton?

Storing large items with DDB and S3

Leave a Reply Cancel reply

About Me

Principal Software Engineer and an industry leader with startup and FAANG experience. I specialize in distributed systems, storage, data protection services and payment processors.

Beyond technical expertise, I am passionate about supporting fellow engineers in their careers. Through approachable blogs and hands-on guidance, I help navigate the ever-evolving landscape of technology, empowering individuals to thrive in their professional journeys.

Open LinkedIn

Recent Posts

  • Building a Delayed Message System with Redis and FastAPI
  • Go Concurrency, Practical Example
  • Using GORM – Part 3: Models and Idempotency
  • Using GORM – Part 2: Transactions and Save Points
  • Using GORM – Part 1: Introduction

Archives

  • January 2025
  • December 2024
  • March 2023
  • February 2023
  • September 2022
  • July 2022
  • July 2021
  • June 2021
  • February 2021
  • April 2018
  • March 2018
  • January 2018
  • July 2017
  • June 2017
  • May 2017

Categories

  • AWS
  • Career Growth
  • Cyber Security
  • Debugging
  • Development
  • Storage
  • Tips & Tricks

Tags

API AWS Azure Bash Brainfuck C++ Challenge Cloud Cloud Bursting Concurrency Database DevOps Disassembly DLL Documentation DynamoDB Go Golang Guice Java Jenkins Mossad NoSQL OOP Performance Programming Python Redis Security Serverless Singleton Streams Testing Unit Tests WebService

All Rights Reserved 2025 © Sirotin Enterprises Inc.
Proudly powered by WordPress | Theme: Doo by ThemeVS.