Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a custom @Inject annotation

Tags:

lombok

guice

I'm using lombok in my project, with a lot of classes with @RequiredArgsConstructors. I'd like these constructors to be automatically used in guice.

One option that works is to use @RequiredArgsConstructors(onConstructor=@__(@Inject)) but this is both ugly and experimental (read: likely to disappear from lombok in the future).

What I'm thinking of doing is to make a custom injection annotation, say @InjectOnlyConstructor to put on my class definition and use guice's SPIs to bind these types. But I can't figure out how to discover these types in the SPI.

How can I look through guice's elements and bind to these constructors that guice would by default reject?

Example of what I want a class to look like:

@Singleton
@InjectOnlyConstructor
@RequiredArgsConstructor
public class CatPictureService {
    private final WebServiceClient client;

    // Cool stuff that would make facebook cry
}

As a backup plan, I can have a module that scans my project's packages and automatically binds those types.

like image 872
Michael Deardeuff Avatar asked Oct 01 '15 18:10

Michael Deardeuff


1 Answers

I'm starting to think it is impossible to hook into Guice to resolve unbound types.

So I've come up with four different solutions. The first is my favorite, it embraces the seemingly explicit wiring pattern the official Guice extensions use. You use it as follows:

public class Bar {
    private Bar(XYZ xyz) { ... }
}

Injector inject = Guice.createInjector(new AbstractModule() {
    protected void configure() {
        install(new OnlyConstructorBuilder(Bar.class));
    }
});

OnlyConstructorBuilder is defined as such:

@RequiredArgsConstructor
private static class OnlyConstructorBuilder extends AbstractModule {
    private final Class<?> type;

    @Override
    protected void configure() {
        bindToOnlyConstructor(type);
    }

    @SuppressWarnings("unchecked")
    private <T> void bindToOnlyConstructor(Class<T> type) {
        Constructor<T>[] ctors = (Constructor<T>[])type.getDeclaredConstructors();

        if (ctors.length > 1) {
            addError("%s has too many constructors %s", type.getName(), Arrays.toString(ctors));
            return;
        } else if (ctors.length < 1) {
            addError("%s needs at least one constructor", type.getName());
            return;
        } else {
            bind(type).toConstructor(ctors[0]);
        }
    }
}

The second approach is to scan the class path—or portions of it—looking for a specific annotation. This was my original idea and it's not too hard if you're using Guava's reflection helpers. I've done something similar before, but people find it too magic. The binding portion is similar to above.

The third approach is to bind(Bar.class) in a module and use the Elements SPI to seek out the UntargetedBindings with a certain annotation. But since I'm already binding the class, I might as well bind to the only constructor.

The fourth approach is just the worst. It uses the Elements API and determines the dependencies of every binding; searching for unbound dependencies with a certain annotation. This is a road I don't want to go down.

like image 163
Michael Deardeuff Avatar answered Jan 02 '23 09:01

Michael Deardeuff