Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom annotations during classpath scanning #419

Open
muliyul opened this issue Jan 28, 2025 · 7 comments
Open

Support custom annotations during classpath scanning #419

muliyul opened this issue Jan 28, 2025 · 7 comments

Comments

@muliyul
Copy link

muliyul commented Jan 28, 2025

The goal is to achieve something similar presented here (timestamp included).

@xvik
Copy link
Owner

xvik commented Jan 28, 2025

The suggestion is to bring spirng-boot configuration annotations style. Very simplified:

@ComponentScan(include = @IncludeFilter({Ann1.class, Ann2.class}))

In the video, motivation is: we can implement some stubs and easilly enable them by applying special annotation to component scan ({Component.class, Stub.class}).


In dropwizard (even in pure), the correct way to implement stubs would be a separate StubsBundle:

bootstrap.addBundle(new StubsBundle())

The result is the same, but dropwizard bundle activation is better (clearly states what was added).

In case of guicey, there is already an installer concept: for example, you might implement StubInstaller which would search for classes annotated with @Stub and register them. And, yes, it's not that simple as configuring annotation, but, from the other side, I don't think such installer would be a good idea anyway - bundle is a much better option.

So sorry, but I don't see a useful implrovement here.

@muliyul
Copy link
Author

muliyul commented Jan 29, 2025

Can you give an example of how to implement such installer?

@xvik
Copy link
Owner

xvik commented Jan 29, 2025

First we need to recognize extension by searching classes with @Stab annotation:

public class StubInstaller implemets FeatureInstaller {
    @Override
    public boolean matches(final Class<?> type) {
        // detect required extension
        return FeatureUtils.hasAnnotation(type, Stub.class);
    }
}

Next step depends on what we want to do:

  • TypeInstaller - class is enought to register extension (e.g. register jersey extension by class in Environment)
  • InstanceInstaller - same as type, but we deal with instance, obtained from injector (e.g. register Manager object in Environment)
  • BindingInstaller - if we need to bind detected extension in guice context (in a special manner). As an example, PluginInstaller, which aggregates plugins into multibindings blocks. Or it could be used to react on extension, detected in guice bingind (e.g. EargerSingletonInstaller validates binding scope, PluginInstaller use it as an alternative way to register plugin). If you don't implement binding installer then extension would be simply bind(Ext.class) in guice context.

For simplicity, suppose Stub is some jersey extension:

public class StubInstaller implemets FeatureInstaller, TypeInstaller {
    @Override
    public boolean matches(final Class<?> type) {
        // detect required extension
        return FeatureUtils.hasAnnotation(type, Stub.class);
    }

    @Override
    public void install(final Environment environment, final Class<Object> type) {
        // apply (inctall) extension: in this case just register in jersey 
        environment.jersey().register(type);
    }
}

Note that installer itself would also be auto-installed by classpath scan (or it must be declared manually in bundle).

Link to docs


But, I don't say that custom installer is a good way to go in case of stubs: most likel, stubs would be a rest resources, so custom bundle would be much simplier:

public class StubsBundle implements GuiceyBundle {
     @Override
     void initialize(GuiceyBootstrap bootstrap) {
         bootstrap.extensions(
                Stub1.class, 
                Stub2.class,
                Stub3.class
         );
     }
}
public class MyApp extends Application<Configuration> {
     @Override
     void initialize(Bootstrap<Configuration> bootstrap) {
         bootstrap.addBundle(GuiceBundle.builder()
                 .bundles(new StubsBundle())
                 .build())
     }
}

If you have some other use-case in mind, please describe it. This way, I would be able to suggest somethig in context of your problem, or I could agree with you that new feature is required for your case.

@muliyul
Copy link
Author

muliyul commented Jan 29, 2025

The use case is similar to the one from the video. The goal is to bind stubs until a real implementation exists.

@xvik
Copy link
Owner

xvik commented Jan 29, 2025

If stubs are in the same project then you can move them outside application package:

com/
    myapp/
        App.class
    stubs/
        Stub1.class

Then stubs could be added by only adding new package for scan:

public class MyApp extends Application<Configuration> {
     @Override
     void initialize(Bootstrap<Configuration> bootstrap) {
         bootstrap.addBundle(GuiceBundle.builder()
                 .enableAutoConfig(
                        "com.myapp",

                       // comment anytime to disable stubs
                        "com.stubs"
                 )
                 .build())
     }
}

Almost the same as with custom annotation.


If stubs must be inside project, then, I suppose, the main problem is how to exclude them all at once.

In this case you can use .disable() predicate:

        @Override
        void initialize(Bootstrap<Configuration> bootstrap) {
            bootstrap.addBundle(GuiceBundle.builder()
                    .enableAutoConfig()
                
                  // uncomment to exclude stubs
                 //   .disable(item -> item.getType().isAnnotationPresent(Stub.class))
                    .build()
            );
        }

Here I assume that all stubs are rest resources, which would be detected by classpath scan. We will use @Stub annotation only to disable them (they will still be detected, but not actually installed).

xvik added a commit that referenced this issue Feb 6, 2025
… -> !cls.isAnnotationPresent(Skip.class))

 Could be used either to skip some classes from scanning (without @InvisibleForScanner) annotation or to accept only annotated classes (spring style) (#419)
@xvik
Copy link
Owner

xvik commented Feb 6, 2025

For the upcoming version added auto scan filters support.
For example, to skip all stubs registration:

GuiceBundle.builder()
     // accept all except annotated classes
     .autoScanFilter(type -> !type.isAnnotationPresent(Stub.class))

Or reverse style (spring):

GuiceBundle.builder()
     // accept ONLY annotated extensions
     .autoScanFilter(type -> type.isAnnotationPresent(Component.class) || type.isAnnotationPresent(Stub.class))

In contrast to .disable(predicate) this does not tries to detect extensions in the filtered classes.

Filter also affects recognition from guice bindings: for example:

@Component
class Ext1 {}

class Ext2 {}

GuiceBundle.builder()
     .autoScanFilter(type -> type.isAnnotationPresent(Component.class) || type.isAnnotationPresent(Stub.class))
     .modules(binder - >  {
           binder.bind(Ext1);
           binder.bind(Ext2);
      })

Only Ext1 would be recognized as extension (Ext2 ignored due to filter). Note that there is no classpath scan in this example - only bindings analysis.

@muliyul
Copy link
Author

muliyul commented Feb 6, 2025

This is great! Thank you for all the hard work 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants