├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .github ├── dependabot.yml └── workflows │ └── CI.yml ├── src ├── test │ └── java │ │ └── ru │ │ └── vyarus │ │ └── guice │ │ └── ext │ │ ├── postprocess │ │ ├── support │ │ │ ├── Bean3.java │ │ │ ├── Bean1.java │ │ │ ├── Bean2.java │ │ │ ├── AbstractBean.java │ │ │ ├── ExceptionalBean.java │ │ │ └── PostProcessor.java │ │ └── TypePostProcessorTest.java │ │ ├── generator │ │ ├── support │ │ │ ├── classloader │ │ │ │ └── SampleBean.java │ │ │ ├── AbstractBean.java │ │ │ ├── anchor │ │ │ │ ├── RootService.java │ │ │ │ ├── DynamicService.java │ │ │ │ ├── TestIface.java │ │ │ │ ├── PureAbstractClass.java │ │ │ │ ├── CtorAbstractClass.java │ │ │ │ └── GenericsCtorAbstractClass.java │ │ │ ├── bad │ │ │ │ ├── WrongUsageBean.java │ │ │ │ ├── BadDeclarationBean.java │ │ │ │ └── BadSingletonDeclaration.java │ │ │ ├── aop │ │ │ │ ├── CustomAopInterceptor.java │ │ │ │ └── CustomAop.java │ │ │ ├── ProvidedAbstractBean.java │ │ │ ├── composite │ │ │ │ ├── CompositeCase2.java │ │ │ │ └── CompositeCase1.java │ │ │ ├── ctor │ │ │ │ ├── Ann.java │ │ │ │ ├── CustomConstructorBean.java │ │ │ │ └── GenerifiedConstructorBean.java │ │ │ ├── ProvidedInterfaceBean.java │ │ │ ├── InterfaceBean.java │ │ │ └── AnnotationCheck.java │ │ ├── ClassloaderCheckTest.java │ │ ├── ConcurrentGenerationTest.java │ │ ├── GeneratorAnchorsTest.java │ │ └── GeneratorTest.java │ │ ├── log │ │ ├── InstanceBindingTest.java │ │ └── LogTest.java │ │ ├── postconstruct │ │ ├── InheritanceTest.java │ │ └── PostConstructTest.java │ │ ├── ModuleTest.java │ │ └── predestroy │ │ ├── DestroyableTest.java │ │ └── PreDestroyTest.java └── main │ └── java │ ├── ru │ └── vyarus │ │ └── guice │ │ └── ext │ │ ├── core │ │ ├── generator │ │ │ ├── DynamicClassException.java │ │ │ ├── anchor │ │ │ │ ├── AnchorBean.java │ │ │ │ └── GeneratorAnchorModule.java │ │ │ ├── ScopeAnnotation.java │ │ │ ├── JavassistUtils.java │ │ │ ├── AnnotationMemberValueVisitor.java │ │ │ └── DynamicClassGenerator.java │ │ ├── type │ │ │ ├── TypePostProcessor.java │ │ │ └── GeneralTypeListener.java │ │ ├── util │ │ │ ├── ObjectPackageMatcher.java │ │ │ └── Utils.java │ │ ├── field │ │ │ ├── FieldPostProcessor.java │ │ │ └── AnnotatedFieldTypeListener.java │ │ └── method │ │ │ ├── MethodPostProcessor.java │ │ │ └── AnnotatedMethodTypeListener.java │ │ ├── log │ │ ├── Log.java │ │ └── Slf4jLogAnnotationProcessor.java │ │ ├── managed │ │ ├── destroyable │ │ │ ├── AnnotatedMethodDestroyable.java │ │ │ ├── Destroyable.java │ │ │ └── DestroyableManager.java │ │ ├── PostConstructAnnotationProcessor.java │ │ ├── DestroyableTypeProcessor.java │ │ └── PreDestroyAnnotationProcessor.java │ │ └── ExtAnnotationsModule.java │ └── com │ └── google │ └── inject │ └── internal │ ├── DynamicSingletonProvider.java │ └── DynamicClassProvider.java ├── .yo-rc.json ├── .appveyor.yml ├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── gradlew.bat ├── gradlew └── README.md /gradle.properties: -------------------------------------------------------------------------------- 1 | version=2.0.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xvik/guice-ext-annotations/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = 'guice-ext-annotations' 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "23:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/support/Bean3.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess.support; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 06.01.2015 6 | */ 7 | public class Bean3 { 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/support/Bean1.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess.support; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 06.01.2015 6 | */ 7 | public class Bean1 extends AbstractBean { 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/support/Bean2.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess.support; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 06.01.2015 6 | */ 7 | public class Bean2 extends AbstractBean { 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/classloader/SampleBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.classloader; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 01.06.2016 6 | */ 7 | public interface SampleBean { 8 | 9 | String sample(); 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/support/AbstractBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess.support; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 06.01.2015 6 | */ 7 | public abstract class AbstractBean { 8 | public int called; 9 | 10 | public void call() { 11 | called++; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/AbstractBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support; 2 | 3 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 10.12.2014 8 | */ 9 | public abstract class AbstractBean { 10 | 11 | @CustomAop 12 | public abstract String hello(); 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/support/ExceptionalBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess.support; 2 | 3 | /** 4 | * @author Vyacheslav Rusakov 5 | * @since 06.01.2015 6 | */ 7 | public class ExceptionalBean extends AbstractBean{ 8 | 9 | @Override 10 | public void call() { 11 | throw new IllegalStateException("Bad"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/anchor/RootService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.anchor; 2 | 3 | /** 4 | * Service must be resolved with JIT and so be created at parent injector 5 | * (so anchor addition is still required to move generated bean to child injector) 6 | * 7 | * @author Vyacheslav Rusakov 8 | * @since 21.09.2016 9 | */ 10 | public class RootService { 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/anchor/DynamicService.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.anchor; 2 | 3 | import com.google.inject.Inject; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 22.09.2016 8 | */ 9 | public class DynamicService { 10 | @Inject 11 | TestIface service; 12 | 13 | public String hello() { 14 | return service.hello(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/bad/WrongUsageBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.bad; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicClassProvider; 5 | 6 | /** 7 | * Class generation can't be used with non abstract types 8 | * 9 | * @author Vyacheslav Rusakov 10 | * @since 10.12.2014 11 | */ 12 | @ProvidedBy(DynamicClassProvider.class) 13 | public class WrongUsageBean { 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/anchor/TestIface.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.anchor; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicSingletonProvider; 5 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 6 | 7 | /** 8 | * @author Vyacheslav Rusakov 9 | * @since 21.09.2016 10 | */ 11 | @ProvidedBy(DynamicSingletonProvider.class) 12 | public interface TestIface { 13 | 14 | @CustomAop 15 | String hello(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/DynamicClassException.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator; 2 | 3 | /** 4 | * Indicates error during dynamic class generation. 5 | * 6 | * @author Vyacheslav Rusakov 7 | * @see ru.vyarus.guice.ext.core.generator.DynamicClassGenerator 8 | * @since 10.12.2014 9 | */ 10 | public class DynamicClassException extends RuntimeException { 11 | 12 | public DynamicClassException(final String message, final Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/support/PostProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess.support; 2 | 3 | import ru.vyarus.guice.ext.core.type.TypePostProcessor; 4 | 5 | /** 6 | * @author Vyacheslav Rusakov 7 | * @since 06.01.2015 8 | */ 9 | public class PostProcessor implements TypePostProcessor { 10 | 11 | public int called; 12 | 13 | @Override 14 | public void process(AbstractBean instance) throws Exception { 15 | instance.call(); 16 | called++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-lib-java": { 3 | "githubUser": "xvik", 4 | "authorName": "Vyacheslav Rusakov", 5 | "authorEmail": "vyarus@gmail.com", 6 | "libName": "guice-ext-annotations", 7 | "libGroup": "ru.vyarus", 8 | "libPackage": "ru.vyarus.guice.ext", 9 | "libVersion": "0.1.0", 10 | "libDesc": "Guice annotations extensions", 11 | "enableQualityChecks": true, 12 | "usedGeneratorVersion": "3.0.0", 13 | "multiModule": false, 14 | "modulePrefix": "-", 15 | "moduleName": "-" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/aop/CustomAopInterceptor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.aop; 2 | 3 | import org.aopalliance.intercept.MethodInterceptor; 4 | import org.aopalliance.intercept.MethodInvocation; 5 | 6 | /** 7 | * @author Vyacheslav Rusakov 8 | * @since 10.12.2014 9 | */ 10 | public class CustomAopInterceptor implements MethodInterceptor{ 11 | 12 | @Override 13 | public Object invoke(MethodInvocation invocation) throws Throwable { 14 | return "I'm intercepted!"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/anchor/PureAbstractClass.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.anchor; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicSingletonProvider; 5 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 6 | 7 | /** 8 | * @author Vyacheslav Rusakov 9 | * @since 21.09.2016 10 | */ 11 | @ProvidedBy(DynamicSingletonProvider.class) 12 | public abstract class PureAbstractClass { 13 | 14 | @CustomAop 15 | public abstract String hello(); 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/aop/CustomAop.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.aop; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 10.12.2014 13 | */ 14 | @Target(METHOD) 15 | @Retention(RUNTIME) 16 | public @interface CustomAop { 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/bad/BadDeclarationBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.bad; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicClassProvider; 5 | 6 | import com.google.inject.Singleton; 7 | 8 | /** 9 | * Scope annotation can't be used directly on abstract type (guice doesn't allow it) 10 | * @author Vyacheslav Rusakov 11 | * @since 10.12.2014 12 | */ 13 | @Singleton 14 | @ProvidedBy(DynamicClassProvider.class) 15 | public abstract class BadDeclarationBean { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/ProvidedAbstractBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.Provider; 5 | import com.google.inject.internal.DynamicClassProvider; 6 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 7 | 8 | /** 9 | * @author Vyacheslav Rusakov 10 | * @since 10.12.2014 11 | */ 12 | @ProvidedBy(DynamicClassProvider.class) 13 | public abstract class ProvidedAbstractBean { 14 | 15 | @CustomAop 16 | public abstract String hello(); 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/bad/BadSingletonDeclaration.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.bad; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.Singleton; 5 | import com.google.inject.internal.DynamicSingletonProvider; 6 | import ru.vyarus.guice.ext.core.generator.ScopeAnnotation; 7 | 8 | /** 9 | * Singleton declaration duplication not allowed 10 | * 11 | * @author Vyacheslav Rusakov 12 | * @since 06.01.2015 13 | */ 14 | @ScopeAnnotation(Singleton.class) 15 | @ProvidedBy(DynamicSingletonProvider.class) 16 | public interface BadSingletonDeclaration { 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/composite/CompositeCase2.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.composite; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicClassProvider; 5 | import ru.vyarus.guice.ext.generator.support.InterfaceBean; 6 | 7 | /** 8 | * Additional methods provided by interface 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 10.12.2014 12 | */ 13 | @ProvidedBy(DynamicClassProvider.class) 14 | public abstract class CompositeCase2 implements InterfaceBean { 15 | 16 | public String self(){ 17 | return "self"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/log/Log.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.log; 2 | 3 | import com.google.inject.ScopeAnnotation; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.FIELD; 10 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 11 | 12 | /** 13 | * Log annotation must be used on {@code org.slf4j.Logger} fields to automatically inject logger instance. 14 | */ 15 | @ScopeAnnotation 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target(FIELD) 19 | public @interface Log { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/composite/CompositeCase1.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.composite; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicClassProvider; 5 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 6 | 7 | /** 8 | * Additional method with direct aop 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 10.12.2014 12 | */ 13 | @ProvidedBy(DynamicClassProvider.class) 14 | public abstract class CompositeCase1 { 15 | 16 | public String self(){ 17 | return "self"; 18 | } 19 | 20 | @CustomAop 21 | public abstract String hello(); 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/ctor/Ann.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.ctor; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 7 | import static java.lang.annotation.ElementType.METHOD; 8 | import static java.lang.annotation.ElementType.PARAMETER; 9 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 10 | 11 | /** 12 | * @author Vyacheslav Rusakov 13 | * @since 11.12.2014 14 | */ 15 | @Target({PARAMETER, CONSTRUCTOR}) 16 | @Retention(RUNTIME) 17 | public @interface Ann { 18 | 19 | String value(); 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/ProvidedInterfaceBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.Singleton; 5 | import com.google.inject.internal.DynamicClassProvider; 6 | import ru.vyarus.guice.ext.core.generator.ScopeAnnotation; 7 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 8 | 9 | /** 10 | * @author Vyacheslav Rusakov 11 | * @since 10.12.2014 12 | */ 13 | @ScopeAnnotation(Singleton.class) 14 | @ProvidedBy(DynamicClassProvider.class) 15 | public interface ProvidedInterfaceBean { 16 | 17 | @CustomAop 18 | String hello(); 19 | 20 | String badMethod(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/type/TypePostProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.type; 2 | 3 | /** 4 | * Type post processor (type searched by exact type, subclass or implemented interface). 5 | * 6 | * @author Vyacheslav Rusakov 7 | * @since 30.06.2014 8 | * @param bean type 9 | */ 10 | public interface TypePostProcessor { 11 | 12 | /** 13 | * Called to post process bean. 14 | * It is safe to avoid explicit exception handling (except special cases required by processor logic). 15 | * 16 | * @param instance bean instance 17 | * @throws Exception on any unrecoverable error 18 | * @see ru.vyarus.guice.ext.core.type.GeneralTypeListener 19 | */ 20 | void process(T instance) throws Exception; 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/anchor/CtorAbstractClass.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.anchor; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicSingletonProvider; 5 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 6 | 7 | import com.google.inject.Inject; 8 | 9 | /** 10 | * @author Vyacheslav Rusakov 11 | * @since 21.09.2016 12 | */ 13 | @ProvidedBy(DynamicSingletonProvider.class) 14 | public abstract class CtorAbstractClass { 15 | 16 | private final RootService service; 17 | 18 | @Inject 19 | public CtorAbstractClass(RootService service) { 20 | this.service = service; 21 | } 22 | 23 | @CustomAop 24 | public abstract String hello(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/anchor/AnchorBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator.anchor; 2 | 3 | /** 4 | * Dummy bean used as anchor dependency for generated classes bindings (JIT bindings) to force them 5 | * to be registered in (for example) child injector. 6 | *

7 | * For abstract classes annotated with {@code @ProvidedBy(DynamicClassProvider)} (or singleton variant), 8 | * provider will check if {@link AnchorBean} is available in injector and add extra dependency for it in 9 | * generated class constructor. 10 | *

11 | * Used by {@link GeneratorAnchorModule}. 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @see GeneratorAnchorModule for details 15 | * @since 21.09.2016 16 | */ 17 | public final class AnchorBean { 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/log/Slf4jLogAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.log; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import ru.vyarus.guice.ext.core.field.FieldPostProcessor; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | /** 10 | * Injects {@code org.slf4j.Logger} instance into fields annotated with @Log annotation. 11 | * 12 | * @author Vyacheslav Rusakov 13 | * @since 30.06.2014 14 | */ 15 | public class Slf4jLogAnnotationProcessor implements FieldPostProcessor { 16 | 17 | @Override 18 | public void process(final Log annotation, final Field field, final Object instance) throws Exception { 19 | final Logger logger = LoggerFactory.getLogger(field.getDeclaringClass()); 20 | field.set(instance, logger); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/managed/destroyable/AnnotatedMethodDestroyable.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.managed.destroyable; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * Destroyable annotation used to call @PostConstruct annotated methods on context destroy. 7 | * 8 | * @author Vyacheslav Rusakov 9 | * @since 30.06.2014 10 | */ 11 | public class AnnotatedMethodDestroyable implements Destroyable { 12 | 13 | private final Method method; 14 | private final Object instance; 15 | 16 | public AnnotatedMethodDestroyable(final Method method, final Object instance) { 17 | this.method = method; 18 | this.instance = instance; 19 | } 20 | 21 | @Override 22 | public void preDestroy() throws Exception { 23 | method.invoke(instance); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/ctor/CustomConstructorBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.ctor; 2 | 3 | import com.google.inject.Inject; 4 | import com.google.inject.ProvidedBy; 5 | import com.google.inject.internal.DynamicClassProvider; 6 | import ru.vyarus.guice.ext.generator.support.ProvidedInterfaceBean; 7 | 8 | /** 9 | * @author Vyacheslav Rusakov 10 | * @since 10.12.2014 11 | */ 12 | @ProvidedBy(DynamicClassProvider.class) 13 | public abstract class CustomConstructorBean { 14 | 15 | ProvidedInterfaceBean bean; 16 | 17 | @Inject 18 | @Ann("ctor") 19 | public CustomConstructorBean(@Ann("param") ProvidedInterfaceBean bean) throws Exception { 20 | this.bean = bean; 21 | } 22 | 23 | public String hello() { 24 | return bean.hello(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/anchor/GenericsCtorAbstractClass.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.anchor; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicSingletonProvider; 5 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 6 | 7 | import com.google.inject.Inject; 8 | import com.google.inject.Provider; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 22.09.2016 13 | */ 14 | @ProvidedBy(DynamicSingletonProvider.class) 15 | public abstract class GenericsCtorAbstractClass { 16 | 17 | private final Provider service; 18 | 19 | @Inject 20 | public GenericsCtorAbstractClass(Provider service) { 21 | this.service = service; 22 | } 23 | 24 | @CustomAop 25 | public abstract String hello(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/managed/PostConstructAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.managed; 2 | 3 | import ru.vyarus.guice.ext.core.method.MethodPostProcessor; 4 | import ru.vyarus.guice.ext.core.util.Utils; 5 | 6 | import jakarta.annotation.PostConstruct; 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Process bean @PostConstruct annotated methods: executes annotated method just after bean initialization. 11 | * 12 | * @author Vyacheslav Rusakov 13 | * @since 30.06.2014 14 | */ 15 | public class PostConstructAnnotationProcessor implements MethodPostProcessor { 16 | 17 | @Override 18 | public void process(final PostConstruct annotation, final Method method, final Object instance) throws Exception { 19 | Utils.checkNoParams(method); 20 | method.invoke(instance); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/managed/destroyable/Destroyable.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.managed.destroyable; 2 | 3 | /** 4 | * Marker interface for beans which require some finalization logic (@PostConstruct alternative). 5 | * 6 | * @author Vyacheslav Rusakov 7 | * @since 30.06.2014 8 | */ 9 | public interface Destroyable { 10 | 11 | /** 12 | * Called on context shutdown (by default on jvm shutdown), but may be called manually through destroy manager 13 | * {@code ru.vyarus.guice.ext.managed.destroyable.DestroyableManager#destroy()}. 14 | * Will be called one time no matter how many time destroy will be asked by manager. 15 | * It is safe to avoid explicit exception handling (except special cases required by logic). 16 | * 17 | * @throws Exception on any unrecoverable error 18 | */ 19 | void preDestroy() throws Exception; 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/log/InstanceBindingTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.log; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.Guice; 5 | import org.junit.Test; 6 | import ru.vyarus.guice.ext.ExtAnnotationsModule; 7 | 8 | import static org.junit.Assert.assertNotNull; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 20.12.2014 13 | */ 14 | public class InstanceBindingTest { 15 | 16 | @Test 17 | public void testInstanceBinding() throws Exception { 18 | LogTest.OkBean bean = Guice.createInjector(new ExtAnnotationsModule(), new AbstractModule() { 19 | @Override 20 | protected void configure() { 21 | bind(LogTest.OkBean.class).toInstance(new LogTest.OkBean()); 22 | } 23 | }).getInstance(LogTest.OkBean.class); 24 | assertNotNull(bean.logger); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/managed/DestroyableTypeProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.managed; 2 | 3 | import ru.vyarus.guice.ext.core.type.TypePostProcessor; 4 | import ru.vyarus.guice.ext.managed.destroyable.Destroyable; 5 | import ru.vyarus.guice.ext.managed.destroyable.DestroyableManager; 6 | 7 | /** 8 | * Registers beans implementing {@code Destroyable} interface to {@code DestroyableManager} to be executed on shutdown. 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 30.06.2014 12 | */ 13 | public class DestroyableTypeProcessor implements TypePostProcessor { 14 | private final DestroyableManager manager; 15 | 16 | public DestroyableTypeProcessor(final DestroyableManager manager) { 17 | this.manager = manager; 18 | } 19 | 20 | @Override 21 | public void process(final Destroyable instance) { 22 | manager.register(instance); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/ctor/GenerifiedConstructorBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support.ctor; 2 | 3 | import com.google.inject.ProvidedBy; 4 | import com.google.inject.internal.DynamicSingletonProvider; 5 | import ru.vyarus.guice.ext.generator.support.ProvidedInterfaceBean; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Provider; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 05.01.2015 13 | */ 14 | @ProvidedBy(DynamicSingletonProvider.class) 15 | public abstract class GenerifiedConstructorBean { 16 | 17 | Provider beanProvider; 18 | 19 | @Inject 20 | public GenerifiedConstructorBean(Provider beanProvider) throws Exception { 21 | this.beanProvider = beanProvider; 22 | } 23 | 24 | public String hello() { 25 | return beanProvider.get().hello(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | environment: 4 | matrix: 5 | - job_name: Java 8 6 | JAVA_HOME: C:\Program Files\Java\jdk1.8.0 7 | - job_name: Java 11 8 | JAVA_HOME: C:\Program Files\Java\jdk11 9 | - job_name: Java 16 10 | appveyor_build_worker_image: Visual Studio 2019 11 | JAVA_HOME: C:\Program Files\Java\jdk16 12 | 13 | build_script: 14 | - ./gradlew assemble --no-daemon 15 | test_script: 16 | - ./gradlew check --no-daemon 17 | 18 | on_success: 19 | - ./gradlew jacocoTestReport --no-daemon 20 | - ps: | 21 | $ProgressPreference = 'SilentlyContinue' 22 | Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe 23 | .\codecov.exe -f build\reports\jacoco\test\jacocoTestReport.xml -F windows 24 | 25 | cache: 26 | - C:\Users\appveyor\.gradle\caches 27 | - C:\Users\appveyor\.gradle\wrapper -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/util/ObjectPackageMatcher.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.util; 2 | 3 | import com.google.inject.TypeLiteral; 4 | import com.google.inject.matcher.AbstractMatcher; 5 | 6 | /** 7 | * Object class matcher. 8 | * Useful to limit post processors appliance scope by specific package (and sub packages) 9 | * 10 | * @author Vyacheslav Rusakov 11 | * @since 30.06.2014 12 | * @param matched object type 13 | */ 14 | public class ObjectPackageMatcher extends AbstractMatcher { 15 | private final String pkg; 16 | 17 | public ObjectPackageMatcher(final String pkg) { 18 | this.pkg = pkg; 19 | } 20 | 21 | @Override 22 | public boolean matches(final T o) { 23 | final Class type = o instanceof TypeLiteral ? ((TypeLiteral) o).getRawType() : o.getClass(); 24 | return Utils.isPackageValid(type) && type.getPackage().getName().startsWith(pkg); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/field/FieldPostProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.field; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | 6 | /** 7 | * Annotated filed post processor. 8 | * 9 | * @author Vyacheslav Rusakov 10 | * @since 30.06.2014 11 | * @param annotation type 12 | */ 13 | public interface FieldPostProcessor { 14 | 15 | /** 16 | * Called to post process annotated bean filed. 17 | * It is safe to avoid explicit exception handling (except special cases required by processor logic). 18 | * 19 | * @param annotation annotation instance 20 | * @param field annotated field 21 | * @param instance bean instance 22 | * @throws Exception on any unrecoverable error 23 | * @see ru.vyarus.guice.ext.core.field.AnnotatedFieldTypeListener 24 | */ 25 | void process(T annotation, Field field, Object instance) throws Exception; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/method/MethodPostProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.method; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | 6 | /** 7 | * Annotated method post processor. 8 | * 9 | * @author Vyacheslav Rusakov 10 | * @since 30.06.2014 11 | * @param annotation type 12 | */ 13 | public interface MethodPostProcessor { 14 | 15 | /** 16 | * Called to post process annotated bean method. 17 | * It is safe to avoid explicit exception handling (except special cases required by processor logic). 18 | * 19 | * @param annotation annotation instance 20 | * @param method annotated method 21 | * @param instance bean instance 22 | * @throws Exception on any unrecoverable error 23 | * @see ru.vyarus.guice.ext.core.method.AnnotatedMethodTypeListener 24 | */ 25 | void process(T annotation, Method method, Object instance) throws Exception; 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postconstruct/InheritanceTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postconstruct; 2 | 3 | import com.google.inject.Guice; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | import ru.vyarus.guice.ext.ExtAnnotationsModule; 7 | 8 | import jakarta.annotation.PostConstruct; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 20.12.2014 13 | */ 14 | public class InheritanceTest { 15 | 16 | @Test 17 | public void testInheritance() throws Exception { 18 | Bean bean = Guice.createInjector(new ExtAnnotationsModule()).getInstance(Bean.class); 19 | Assert.assertEquals(2, bean.counter); 20 | } 21 | 22 | public static class Bean extends BaseBean { 23 | 24 | } 25 | 26 | public static abstract class BaseBean { 27 | int counter; 28 | 29 | @PostConstruct 30 | public void init() { 31 | counter++; 32 | } 33 | 34 | @PostConstruct 35 | private void init2() { 36 | counter++; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | name: Java ${{ matrix.java }} 11 | strategy: 12 | matrix: 13 | java: [8, 11, 16] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Set up JDK ${{ matrix.java }} 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: ${{ matrix.java }} 22 | 23 | - name: Build 24 | run: | 25 | chmod +x gradlew 26 | ./gradlew assemble --no-daemon 27 | 28 | - name: Test 29 | env: 30 | GH_ACTIONS: true 31 | run: ./gradlew check --no-daemon 32 | 33 | - name: Build coverage report 34 | run: ./gradlew jacocoTestReport --no-daemon 35 | 36 | - uses: codecov/codecov-action@v4 37 | with: 38 | files: build/reports/jacoco/test/jacocoTestReport.xml 39 | flags: LINUX 40 | fail_ci_if_error: true 41 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/ScopeAnnotation.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Guice doesn't allow scope annotations on abstract types 12 | * ({@code com.google.inject.internal.Annotations#checkForMisplacedScopeAnnotations()}), so you can't use it directly. 13 | *

This annotation wraps actual scope annotation which will be set to generated class.

14 | *

And yes, it's named as guice marker annotation, but name is perfect and you'll never need to use both in one 15 | * class.

16 | * 17 | * @author Vyacheslav Rusakov 18 | * @since 10.12.2014 19 | */ 20 | @Target(TYPE) 21 | @Retention(RUNTIME) 22 | public @interface ScopeAnnotation { 23 | 24 | /** 25 | * @return scope annotation 26 | */ 27 | Class value(); 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/ModuleTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import org.junit.Test; 6 | import org.slf4j.Logger; 7 | import ru.vyarus.guice.ext.log.Log; 8 | 9 | import static org.junit.Assert.assertNotNull; 10 | import static org.junit.Assert.assertNull; 11 | 12 | /** 13 | * @author Vyacheslav Rusakov 14 | * @since 30.06.2014 15 | */ 16 | public class ModuleTest { 17 | 18 | @Test 19 | public void testWrongPkg() throws Exception { 20 | Injector injector = Guice.createInjector(new ExtAnnotationsModule("wrong.package")); 21 | Bean bean = injector.getInstance(Bean.class); 22 | assertNull(bean.logger); 23 | } 24 | 25 | @Test 26 | public void testGoodPkg() throws Exception { 27 | Injector injector = Guice.createInjector(new ExtAnnotationsModule(Bean.class.getPackage().getName())); 28 | Bean bean = injector.getInstance(Bean.class); 29 | assertNotNull(bean.logger); 30 | } 31 | 32 | public static class Bean { 33 | @Log 34 | Logger logger; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vyacheslav Rusakov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/InterfaceBean.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support; 2 | 3 | import ru.vyarus.guice.ext.core.generator.ScopeAnnotation; 4 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 5 | 6 | import com.google.inject.Singleton; 7 | import java.lang.annotation.ElementType; 8 | 9 | /** 10 | * @author Vyacheslav Rusakov 11 | * @since 10.12.2014 12 | */ 13 | @AnnotationCheck( 14 | ann = @ScopeAnnotation(Singleton.class), 15 | arr = {1,2,3}, 16 | arrBool = {true, false}, 17 | arrByte = {1, 2}, 18 | arrChar = {',', 'l'}, 19 | arrDbl = {0.1, 0.2}, 20 | arrFlt = {0.1f, 0.2f}, 21 | arrLng = {1l, 2l}, 22 | arrShrt = {1,2}, 23 | arrObj = {"tst", "tst2"}, 24 | bool = true, 25 | bte = 1, 26 | chr = ',', 27 | dbl = 1d, 28 | integer = 1, 29 | lng = 1l, 30 | flt = 1, 31 | srt = 1, 32 | str = "string", 33 | cls = InterfaceBean.class, 34 | enm = ElementType.TYPE 35 | ) 36 | public interface InterfaceBean { 37 | 38 | @CustomAop 39 | String hello(); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/managed/PreDestroyAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.managed; 2 | 3 | import ru.vyarus.guice.ext.core.method.MethodPostProcessor; 4 | import ru.vyarus.guice.ext.core.util.Utils; 5 | import ru.vyarus.guice.ext.managed.destroyable.AnnotatedMethodDestroyable; 6 | import ru.vyarus.guice.ext.managed.destroyable.DestroyableManager; 7 | 8 | import jakarta.annotation.PreDestroy; 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * Registers bean methods annotated with @PostConstruct in {@code DestroyableManager} to be called on shutdown. 13 | * 14 | * @author Vyacheslav Rusakov 15 | * @since 30.06.2014 16 | */ 17 | public class PreDestroyAnnotationProcessor implements MethodPostProcessor { 18 | 19 | private final DestroyableManager manager; 20 | 21 | public PreDestroyAnnotationProcessor(final DestroyableManager manager) { 22 | this.manager = manager; 23 | } 24 | 25 | @Override 26 | public void process(final PreDestroy annotation, final Method method, final Object instance) throws Exception { 27 | Utils.checkNoParams(method); 28 | manager.register(new AnnotatedMethodDestroyable(method, instance)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/support/AnnotationCheck.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator.support; 2 | 3 | import ru.vyarus.guice.ext.core.generator.ScopeAnnotation; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 11 | import static java.lang.annotation.ElementType.TYPE; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | /** 15 | * @author Vyacheslav Rusakov 16 | * @since 10.12.2014 17 | */ 18 | @Target(TYPE) 19 | @Retention(RUNTIME) 20 | public @interface AnnotationCheck { 21 | ScopeAnnotation ann(); 22 | int[] arr(); 23 | boolean[] arrBool(); 24 | short[] arrShrt(); 25 | long[] arrLng(); 26 | byte[] arrByte(); 27 | double[] arrDbl(); 28 | float[] arrFlt(); 29 | char[] arrChar(); 30 | String[] arrObj(); 31 | boolean bool(); 32 | byte bte(); 33 | char chr(); 34 | double dbl(); 35 | int integer(); 36 | long lng(); 37 | float flt(); 38 | short srt(); 39 | String str(); 40 | Class cls(); 41 | ElementType enm(); 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/predestroy/DestroyableTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.predestroy; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import ru.vyarus.guice.ext.ExtAnnotationsModule; 8 | import ru.vyarus.guice.ext.managed.destroyable.Destroyable; 9 | import ru.vyarus.guice.ext.managed.destroyable.DestroyableManager; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | /** 14 | * @author Vyacheslav Rusakov 15 | * @since 30.06.2014 16 | */ 17 | public class DestroyableTest { 18 | Injector injector; 19 | DestroyableManager manager; 20 | 21 | @Before 22 | public void setUp() throws Exception { 23 | injector = Guice.createInjector(new ExtAnnotationsModule()); 24 | manager = injector.getInstance(DestroyableManager.class); 25 | } 26 | 27 | @Test 28 | public void testCall() throws Exception { 29 | Bean bean = injector.getInstance(Bean.class); 30 | manager.destroy(); 31 | assertEquals(1, bean.counter); 32 | 33 | } 34 | 35 | public static class Bean implements Destroyable { 36 | int counter; 37 | 38 | @Override 39 | public void preDestroy() throws Exception { 40 | counter++; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/log/LogTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.log; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.google.inject.ProvisionException; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.slf4j.Logger; 9 | import ru.vyarus.guice.ext.ExtAnnotationsModule; 10 | 11 | import static org.junit.Assert.assertNotNull; 12 | 13 | /** 14 | * @author Vyacheslav Rusakov 15 | * @since 30.06.2014 16 | */ 17 | public class LogTest { 18 | Injector injector; 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | injector = Guice.createInjector(new ExtAnnotationsModule()); 23 | } 24 | 25 | @Test 26 | public void testSuccess() throws Exception { 27 | OkBean bean = injector.getInstance(OkBean.class); 28 | assertNotNull(bean.logger); 29 | assertNotNull(bean.logger2); 30 | } 31 | 32 | @Test(expected = ProvisionException.class) 33 | public void testFail() throws Exception { 34 | injector.getInstance(KoBean.class); 35 | } 36 | 37 | public static class OkBean { 38 | @Log 39 | public Logger logger; 40 | @Log 41 | private Logger logger2; 42 | } 43 | 44 | public static class KoBean { 45 | @Log 46 | private java.util.logging.Logger logger2; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/google/inject/internal/DynamicSingletonProvider.java: -------------------------------------------------------------------------------- 1 | package com.google.inject.internal; 2 | 3 | import com.google.inject.Injector; 4 | 5 | import com.google.inject.Inject; 6 | import com.google.inject.Singleton; 7 | import java.lang.annotation.Annotation; 8 | 9 | /** 10 | * Specific version of {@link com.google.inject.internal.DynamicClassProvider}, which applies singleton scope 11 | * to generated classes. 12 | * The main intention is to reduce code size from common case from 13 | * {@code @ScopeAnnotation(Singleton.class) @ProvidedBy(DynamicClassProvider.class)} 14 | * to simply {@code @ProvidedBy(DynamicSingletonProvider.class)}. 15 | *

16 | * If used with injectors hierarchy or within private modules, use together with 17 | * {@link ru.vyarus.guice.ext.core.generator.anchor.GeneratorAnchorModule} to properly scope dynamic bindings. 18 | * 19 | * @author Vyacheslav Rusakov 20 | * @see com.google.inject.internal.DynamicClassProvider for more docs 21 | * @since 05.01.2015 22 | */ 23 | @Singleton 24 | public class DynamicSingletonProvider extends DynamicClassProvider { 25 | 26 | @Inject 27 | public DynamicSingletonProvider(final Injector injector) { 28 | super(injector); 29 | } 30 | 31 | @Override 32 | protected Class getScopeAnnotation() { 33 | return Singleton.class; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/util/Utils.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.util; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * Generic utilities. 7 | * 8 | * @author Vyacheslav Rusakov 9 | * @since 30.06.2014 10 | */ 11 | public final class Utils { 12 | 13 | private Utils() { 14 | } 15 | 16 | /** 17 | * Important check, because JDK proxies of public interfaces have no package 18 | * (thanks to @binkley https://github.com/99soft/lifegycle/pull/5). 19 | * 20 | * @param type class type to check 21 | * @return true if package could be resolved, false otherwise 22 | */ 23 | public static boolean isPackageValid(final Class type) { 24 | boolean res = false; 25 | if (type != null) { 26 | final Package packaj = type.getPackage(); 27 | res = !(packaj == null || packaj.getName().startsWith("java")); 28 | } 29 | return res; 30 | } 31 | 32 | /** 33 | * Checks that method has no parameters, otherwise throws exception. 34 | * 35 | * @param method method to check 36 | */ 37 | public static void checkNoParams(final Method method) { 38 | if (method.getParameterTypes().length > 0) { 39 | throw new IllegalStateException("Method without parameters required"); 40 | } 41 | } 42 | 43 | /** 44 | * Note: versions below 1.8 are not supported. 45 | * 46 | * @return true if current java is 1.8, otherwise assumed 9 or above 47 | */ 48 | public static boolean isJava8() { 49 | final String version = System.getProperty("java.version"); 50 | return version.startsWith("1.8"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postconstruct/PostConstructTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postconstruct; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.google.inject.ProvisionException; 6 | import jakarta.annotation.PostConstruct; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import ru.vyarus.guice.ext.ExtAnnotationsModule; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | /** 14 | * @author Vyacheslav Rusakov 15 | * @since 30.06.2014 16 | */ 17 | public class PostConstructTest { 18 | Injector injector; 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | injector = Guice.createInjector(new ExtAnnotationsModule()); 23 | } 24 | 25 | @Test 26 | public void testSuccess() throws Exception { 27 | OkBean bean = injector.getInstance(OkBean.class); 28 | assertEquals(2, bean.counter); 29 | } 30 | 31 | @Test(expected = ProvisionException.class) 32 | public void testFail() throws Exception { 33 | injector.getInstance(KoBean.class); 34 | } 35 | 36 | @Test(expected = ProvisionException.class) 37 | public void testExceptionFail() throws Exception { 38 | injector.getInstance(KoExceptionBean.class); 39 | } 40 | 41 | public static class OkBean { 42 | private int counter; 43 | 44 | @PostConstruct 45 | public void init() { 46 | counter++; 47 | } 48 | 49 | @PostConstruct 50 | private void init2() { 51 | counter++; 52 | } 53 | } 54 | 55 | public static class KoBean { 56 | @PostConstruct 57 | public void init(Object smth) { 58 | } 59 | } 60 | 61 | public static class KoExceptionBean { 62 | @PostConstruct 63 | public void init() { 64 | throw new IllegalStateException("foo"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/type/GeneralTypeListener.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.type; 2 | 3 | import com.google.inject.TypeLiteral; 4 | import com.google.inject.spi.InjectionListener; 5 | import com.google.inject.spi.TypeEncounter; 6 | import com.google.inject.spi.TypeListener; 7 | import ru.vyarus.guice.ext.core.util.Utils; 8 | 9 | /** 10 | * Generic type listener for bean types (exact class, by base class or beans annotating interface). 11 | * 12 | * @param bean type 13 | * @author Vyacheslav Rusakov 14 | * @since 30.06.2014 15 | */ 16 | public class GeneralTypeListener implements TypeListener { 17 | 18 | private final Class typeClass; 19 | private final TypePostProcessor postProcessor; 20 | 21 | public GeneralTypeListener(final Class typeClass, final TypePostProcessor postProcessor) { 22 | this.typeClass = typeClass; 23 | this.postProcessor = postProcessor; 24 | } 25 | 26 | @Override 27 | @SuppressWarnings("unchecked") 28 | public void hear(final TypeLiteral type, final TypeEncounter encounter) { 29 | final Class actualType = type.getRawType(); 30 | if (!Utils.isPackageValid(actualType)) { 31 | return; 32 | } 33 | if (typeClass.isAssignableFrom(actualType)) { 34 | encounter.register(new InjectionListener() { 35 | @Override 36 | public void afterInjection(final I injectee) { 37 | try { 38 | postProcessor.process((T) injectee); 39 | } catch (Exception ex) { 40 | throw new IllegalStateException( 41 | String.format("Failed to process type %s of class %s", 42 | typeClass.getSimpleName(), injectee.getClass().getSimpleName()), ex); 43 | } 44 | } 45 | }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/ClassloaderCheckTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import ru.vyarus.guice.ext.core.generator.DynamicClassGenerator; 6 | import ru.vyarus.guice.ext.generator.support.classloader.SampleBean; 7 | 8 | import java.net.URL; 9 | import java.net.URLClassLoader; 10 | 11 | /** 12 | * @author Vyacheslav Rusakov 13 | * @since 01.06.2016 14 | */ 15 | public class ClassloaderCheckTest { 16 | 17 | @Test 18 | public void checkClassLoaderBinding() throws Exception { 19 | // custom classloader used to load classes (parent implicitly set to ext instead of system 20 | // to avoid classpath entry load) 21 | final URL[] classpath = {ClassloaderCheckTest.class.getResource("/")}; 22 | final ClassLoader extCL = ClassLoader.getSystemClassLoader().getParent(); 23 | ClassLoader cl = new URLClassLoader(classpath, extCL); 24 | 25 | // loading abstract type 26 | final String typeName = SampleBean.class.getName(); 27 | Class type = cl.loadClass(typeName); 28 | Assert.assertEquals(cl, type.getClassLoader()); 29 | 30 | // generated class must be assigned to custom classloader 31 | Class generated = DynamicClassGenerator.generate(type); 32 | Assert.assertEquals(cl, generated.getClassLoader()); 33 | 34 | // now change classloader to make sure class will be re-generated (correct javassist usage) 35 | ClassLoader cl2 = new URLClassLoader(classpath, extCL); 36 | 37 | // loading abstract type again with different cl 38 | Class type2 = cl2.loadClass(typeName); 39 | Assert.assertEquals(cl2, type2.getClassLoader()); 40 | 41 | // generated class must be assigned to new custom classloader 42 | Class generated2 = DynamicClassGenerator.generate(type2); 43 | Assert.assertEquals(cl2, generated2.getClassLoader()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/ConcurrentGenerationTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import ru.vyarus.guice.ext.core.generator.DynamicClassGenerator; 7 | import ru.vyarus.guice.ext.generator.support.AbstractBean; 8 | import ru.vyarus.guice.ext.generator.support.InterfaceBean; 9 | import ru.vyarus.guice.ext.generator.support.ProvidedInterfaceBean; 10 | import ru.vyarus.guice.ext.generator.support.ctor.CustomConstructorBean; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | import java.util.concurrent.Future; 17 | 18 | /** 19 | * @author Vyacheslav Rusakov 20 | * @since 02.06.2016 21 | */ 22 | public class ConcurrentGenerationTest { 23 | 24 | ExecutorService executor; 25 | 26 | @Before 27 | public void setUp() throws Exception { 28 | executor = Executors.newFixedThreadPool(20); 29 | } 30 | 31 | @After 32 | public void tearDown() throws Exception { 33 | executor.shutdown(); 34 | } 35 | 36 | @Test 37 | public void testConcurrency() throws Exception { 38 | List> executed = new ArrayList>(); 39 | final Class[] types = new Class[]{ 40 | AbstractBean.class, 41 | InterfaceBean.class, 42 | ProvidedInterfaceBean.class, 43 | CustomConstructorBean.class 44 | }; 45 | int count = 20; 46 | for (int i = 0; i < count; i++) { 47 | executed.add( 48 | executor.submit(new Runnable() { 49 | @Override 50 | @SuppressWarnings("unchecked") 51 | public void run() { 52 | DynamicClassGenerator.generate(types[(int)(types.length*Math.random())]); 53 | } 54 | }) 55 | ); 56 | } 57 | for(Future future: executed) { 58 | future.get(); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 2.0.1 (2023-04-13) 2 | * Avoid direct javax.inject annotations usage for jakarta.inject compatibility 3 | (guice native annotations used instead) 4 | - Dynamic class generation would support both annotations 5 | 6 | ### 2.0.0 (2023-04-02) 7 | * Switch to jakarta.annotation-api 2 (jakarta. namespace) 8 | 9 | NOTE: guice still use javax.inject 10 | 11 | ### 1.4.1 (2023-04-02) 12 | * Replace javax.annotation-api jar with jakarta.annotation-api 1 (preserving javax. namespace) 13 | 14 | ### 1.4.0 (2021-09-06) 15 | * Drop java 1.6 and 1.7 support 16 | * Update to guice 5.0.1 17 | * Fix JDK 16 compatibility for class generator (min required javassist is 3.24) 18 | 19 | ### 1.3.0 (2018-04-01) 20 | * Guice 4.2.0 compatibility 21 | 22 | ### 1.2.1 (2016-09-23) 23 | * Fix DynamicClassProvider and DynamicSingletonProvider providers to be singletons. 24 | 25 | ### 1.2.0 (2016-09-23) 26 | * Fix class generation for dynamic class loaders cases (required, for example, for playframework dev mode): 27 | - dynamic classes are checked now against class loader of original type 28 | - different classes will be generated for the same type from different class loaders 29 | * Make class generator thread safe (fixes concurrent provider calls issue) 30 | * Update to guice 4.1 31 | * Add ability to use dynamic providers in child injector or inside private module: GeneratorAnchorModule (bubble up fix) 32 | 33 | ### 1.1.1 (2015-01-06) 34 | * Fix recognition of javax.inject.Inject annotation during class generation 35 | * Generated class now contains valid generics signature (required for Provided parameters) 36 | * Add DynamicSingletonProvider: shortcut for DynamicClassProvider to produce singleton bean (without need for additional annotation) 37 | * Fix type post processor abstract class recognition 38 | 39 | ### 1.1.0 (2014-12-14) 40 | * Update guice 3.0 -> 4.0-beta5 41 | * Add DynamicClassGenerator: interfaces and abstract classes may be used as guice beans with complete aop support 42 | * Add DynamicClassProvider for abstract classes and interfaces to use in @ProvidedBy (JIT resolution will trigger dynamic class generation ) 43 | 44 | ### 1.0.1 (2014-08-16) 45 | * Fix pmd/checkstyle warnings 46 | 47 | ### 1.0.0 (2014-07-02) 48 | * Initial release -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/managed/destroyable/DestroyableManager.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.managed.destroyable; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Manage destroyable instances. 11 | * Implements {@code Runnable} to be used as shutdown hook. 12 | * 13 | * @author Vyacheslav Rusakov 14 | * @since 30.06.2014 15 | */ 16 | public class DestroyableManager implements Runnable { 17 | private final Logger logger = LoggerFactory.getLogger(DestroyableManager.class); 18 | 19 | private final List destroyListeners = new ArrayList<>(); 20 | 21 | /** 22 | * Register destroyable instance to be called on context shutdown. 23 | * Not thread safe (assuming single thread injector initialization) 24 | * 25 | * @param destroyable destroyable instance 26 | * @see ru.vyarus.guice.ext.managed.PostConstructAnnotationProcessor regsters annotated methods 27 | * @see ru.vyarus.guice.ext.managed.DestroyableTypeProcessor registers beans anootated with {@code Destroyable} 28 | */ 29 | public void register(final Destroyable destroyable) { 30 | // assuming single thread injector creation 31 | destroyListeners.add(destroyable); 32 | } 33 | 34 | /** 35 | * Called on context shutdown to call all registered destroyable instances. 36 | * By default called on jvm shutdown, but may be called manually to synchronise with 37 | * some other container shutdown (e.g. web container) 38 | * Safe to call many times, but all destroy instances will be processed only on first call. 39 | * Thread safe. 40 | */ 41 | public void destroy() { 42 | // just for the case 43 | synchronized (this) { 44 | for (Destroyable destroyable : destroyListeners) { 45 | try { 46 | destroyable.preDestroy(); 47 | } catch (Exception ex) { 48 | logger.error("Failed to properly destroy bean", ex); 49 | } 50 | } 51 | destroyListeners.clear(); 52 | } 53 | } 54 | 55 | @Override 56 | public void run() { 57 | destroy(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/predestroy/PreDestroyTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.predestroy; 2 | 3 | import com.google.inject.Guice; 4 | import com.google.inject.Injector; 5 | import com.google.inject.ProvisionException; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import ru.vyarus.guice.ext.ExtAnnotationsModule; 9 | import ru.vyarus.guice.ext.managed.destroyable.DestroyableManager; 10 | 11 | import jakarta.annotation.PreDestroy; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | 15 | /** 16 | * @author Vyacheslav Rusakov 17 | * @since 30.06.2014 18 | */ 19 | public class PreDestroyTest { 20 | Injector injector; 21 | DestroyableManager manager; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | injector = Guice.createInjector(new ExtAnnotationsModule()); 26 | manager = injector.getInstance(DestroyableManager.class); 27 | } 28 | 29 | @Test 30 | public void testSuccess() throws Exception { 31 | OkBean bean = injector.getInstance(OkBean.class); 32 | manager.destroy(); 33 | assertEquals(2, bean.counter); 34 | } 35 | 36 | @Test 37 | public void testDoubleDestroy() throws Exception { 38 | OkBean bean = injector.getInstance(OkBean.class); 39 | manager.destroy(); 40 | manager.destroy(); 41 | assertEquals(2, bean.counter); 42 | } 43 | 44 | @Test(expected = ProvisionException.class) 45 | public void testFail() throws Exception { 46 | injector.getInstance(KoBean.class); 47 | } 48 | 49 | @Test 50 | public void testExceptionFail() throws Exception { 51 | injector.getInstance(KoExceptionBean.class); 52 | manager.destroy(); 53 | } 54 | 55 | public static class OkBean { 56 | private int counter; 57 | 58 | @PreDestroy 59 | public void init() { 60 | counter++; 61 | } 62 | 63 | @PreDestroy 64 | private void init2() { 65 | counter++; 66 | } 67 | } 68 | 69 | public static class KoBean { 70 | @PreDestroy 71 | public void init(Object smth) { 72 | } 73 | } 74 | 75 | public static class KoExceptionBean { 76 | @PreDestroy 77 | public void init() { 78 | throw new IllegalStateException("foo"); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/postprocess/TypePostProcessorTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.postprocess; 2 | 3 | import com.google.inject.*; 4 | import com.google.inject.matcher.Matchers; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import ru.vyarus.guice.ext.core.type.GeneralTypeListener; 8 | import ru.vyarus.guice.ext.postprocess.support.*; 9 | 10 | /** 11 | * @author Vyacheslav Rusakov 12 | * @since 06.01.2015 13 | */ 14 | public class TypePostProcessorTest { 15 | 16 | @Test 17 | public void testPostProcessingByType() throws Exception { 18 | final PostProcessor postProcessor = new PostProcessor(); 19 | Injector injector = Guice.createInjector(new AbstractModule() { 20 | @Override 21 | protected void configure() { 22 | bind(Bean1.class).asEagerSingleton(); 23 | bind(Bean2.class).asEagerSingleton(); 24 | bind(Bean3.class).asEagerSingleton(); 25 | bindListener(Matchers.any(), 26 | new GeneralTypeListener(AbstractBean.class, postProcessor)); 27 | } 28 | }); 29 | Assert.assertEquals(postProcessor.called, 2); 30 | Assert.assertEquals(injector.getInstance(Bean1.class).called, 1); 31 | Assert.assertEquals(injector.getInstance(Bean2.class).called, 1); 32 | } 33 | 34 | @Test(expected = CreationException.class) 35 | public void testPostProcessingFailure() throws Exception { 36 | Guice.createInjector(new AbstractModule() { 37 | @Override 38 | protected void configure() { 39 | bind(ExceptionalBean.class).asEagerSingleton(); 40 | bindListener(Matchers.any(), 41 | new GeneralTypeListener(AbstractBean.class, new PostProcessor())); 42 | } 43 | }); 44 | } 45 | 46 | @Test(expected = ProvisionException.class) 47 | public void testPostProcessingFailure2() throws Exception { 48 | Guice.createInjector(new AbstractModule() { 49 | @Override 50 | protected void configure() { 51 | bind(ExceptionalBean.class).in(Singleton.class); 52 | bindListener(Matchers.any(), 53 | new GeneralTypeListener(AbstractBean.class, new PostProcessor())); 54 | } 55 | }).getInstance(ExceptionalBean.class); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/JavassistUtils.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator; 2 | 3 | import javassist.ClassPool; 4 | import javassist.CtClass; 5 | import javassist.bytecode.ConstPool; 6 | import javassist.bytecode.annotation.Annotation; 7 | import javassist.bytecode.annotation.MemberValue; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * Helper javassist methods. 13 | * 14 | * @author Vyacheslav Rusakov 15 | * @since 06.01.2015 16 | */ 17 | public final class JavassistUtils { 18 | 19 | private JavassistUtils() { 20 | } 21 | 22 | /** 23 | * @param classPool class pool to use 24 | * @param constPool constants pool 25 | * @param ann annotation to copy 26 | * @return javassist annotation object (copy of original annotation) 27 | * @throws Exception on errors 28 | */ 29 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 30 | public static Annotation copyAnnotation(final ClassPool classPool, final ConstPool constPool, 31 | final java.lang.annotation.Annotation ann) throws Exception { 32 | final Class annotationType = ann.annotationType(); 33 | final Annotation copy = new Annotation(annotationType.getName(), constPool); 34 | final Method[] methods = annotationType.getDeclaredMethods(); 35 | for (final Method method : methods) { 36 | final CtClass ctType = classPool.get(method.getReturnType().getName()); 37 | final MemberValue memberValue = Annotation.createMemberValue(constPool, ctType); 38 | final Object value = method.invoke(ann); 39 | memberValue.accept(new AnnotationMemberValueVisitor(classPool, constPool, value)); 40 | copy.addMemberValue(method.getName(), memberValue); 41 | } 42 | return copy; 43 | } 44 | 45 | /** 46 | * @param classPool class pool to use 47 | * @param types java types 48 | * @return array of javassist types 49 | * @throws Exception on errors 50 | */ 51 | public static CtClass[] convertTypes(final ClassPool classPool, final Class... types) throws Exception { 52 | final CtClass[] resTypes = new CtClass[types.length]; 53 | for (int i = 0; i < resTypes.length; i++) { 54 | final Class type = types[i]; 55 | resTypes[i] = classPool.get(type.getName()); 56 | } 57 | return resTypes; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created with https://www.gitignore.io 2 | 3 | ### Gradle ### 4 | .gradle/ 5 | build/ 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | 10 | ### JetBrains ### 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 12 | 13 | /*.iml 14 | 15 | ## Directory-based project format: 16 | .idea/ 17 | 18 | ## File-based project format: 19 | *.ipr 20 | *.iws 21 | 22 | ## Plugin-specific files: 23 | 24 | # IntelliJ 25 | out/ 26 | 27 | # mpeltonen/sbt-idea plugin 28 | .idea_modules/ 29 | 30 | # JIRA plugin 31 | atlassian-ide-plugin.xml 32 | 33 | # Crashlytics plugin (for Android Studio and IntelliJ) 34 | com_crashlytics_export_strings.xml 35 | 36 | 37 | ### Eclipse ### 38 | *.pydevproject 39 | .metadata 40 | bin/ 41 | tmp/ 42 | *.tmp 43 | *.bak 44 | *.swp 45 | *~.nib 46 | local.properties 47 | .settings/ 48 | .loadpath 49 | 50 | # External tool builders 51 | .externalToolBuilders/ 52 | 53 | # Locally stored "Eclipse launch configurations" 54 | *.launch 55 | 56 | # CDT-specific 57 | .cproject 58 | 59 | # PDT-specific 60 | .buildpath 61 | 62 | # sbteclipse plugin 63 | .target 64 | 65 | # TeXlipse plugin 66 | .texlipse 67 | 68 | ### Java ### 69 | *.class 70 | 71 | # Mobile Tools for Java (J2ME) 72 | .mtj.tmp/ 73 | 74 | # Package Files # 75 | *.war 76 | *.ear 77 | 78 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 79 | hs_err_pid* 80 | 81 | 82 | ### NetBeans ### 83 | nbproject/private/ 84 | nbbuild/ 85 | dist/ 86 | nbdist/ 87 | nbactions.xml 88 | nb-configuration.xml 89 | 90 | 91 | ### OSX ### 92 | .DS_Store 93 | .AppleDouble 94 | .LSOverride 95 | 96 | # Icon must end with two \r 97 | Icon 98 | 99 | 100 | # Thumbnails 101 | ._* 102 | 103 | # Files that might appear on external disk 104 | .Spotlight-V100 105 | .Trashes 106 | 107 | # Directories potentially created on remote AFP share 108 | .AppleDB 109 | .AppleDesktop 110 | Network Trash Folder 111 | Temporary Items 112 | .apdisk 113 | 114 | 115 | ### Windows ### 116 | # Windows image file caches 117 | Thumbs.db 118 | ehthumbs.db 119 | 120 | # Folder config file 121 | Desktop.ini 122 | 123 | # Recycle Bin used on file shares 124 | $RECYCLE.BIN/ 125 | 126 | # Windows Installer files 127 | *.cab 128 | *.msi 129 | *.msm 130 | *.msp 131 | 132 | # Windows shortcuts 133 | *.lnk 134 | 135 | 136 | ### Linux ### 137 | *~ 138 | 139 | # KDE directory preferences 140 | .directory 141 | 142 | ### JEnv ### 143 | # JEnv local Java version configuration file 144 | .java-version 145 | 146 | # Used by previous versions of JEnv 147 | .jenv-version 148 | -------------------------------------------------------------------------------- /src/main/java/com/google/inject/internal/DynamicClassProvider.java: -------------------------------------------------------------------------------- 1 | package com.google.inject.internal; 2 | 3 | import com.google.inject.Injector; 4 | import com.google.inject.Key; 5 | import com.google.inject.Provider; 6 | import ru.vyarus.guice.ext.core.generator.DynamicClassGenerator; 7 | import ru.vyarus.guice.ext.core.generator.anchor.AnchorBean; 8 | 9 | import com.google.inject.Inject; 10 | import com.google.inject.Singleton; 11 | import java.lang.annotation.Annotation; 12 | 13 | /** 14 | * Provider allows using interfaces or abstract classes as normal guice beans. 15 | * Simply annotate interface or abstract class with {@code @ProvidedBy(DynamicClassProvider.class)} 16 | * and use it as usual guice bean. 17 | *

Original bean will be correctly handled by guice aop: provider will generate new class, which guice could 18 | * use for proxy generation.

19 | *

If used with injectors hierarchy or within private modules, use together with 20 | * {@link ru.vyarus.guice.ext.core.generator.anchor.GeneratorAnchorModule} to properly scope dynamic bindings.

21 | *

Providers use guice package to use internal guice api (to resolve actual required type)

22 | * 23 | * @author Vyacheslav Rusakov 24 | * @see ru.vyarus.guice.ext.core.generator.DynamicClassGenerator if you prefer direct registration in module 25 | * @since 07.12.2014 26 | */ 27 | @Singleton 28 | public class DynamicClassProvider implements Provider { 29 | 30 | private final Injector injector; 31 | 32 | @Inject 33 | public DynamicClassProvider(final Injector injector) { 34 | this.injector = injector; 35 | } 36 | 37 | @Override 38 | @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.NullAssignment"}) 39 | public Object get() { 40 | try (InternalContext context = ((InjectorImpl) injector).enterContext()) { 41 | // check if (possibly) child context contains anchor bean definition 42 | final boolean hasAnchor = injector.getExistingBinding(Key.get(AnchorBean.class)) != null; 43 | final Class abstractType = context.getDependency().getKey().getTypeLiteral().getRawType(); 44 | final Class generatedType = DynamicClassGenerator.generate(abstractType, getScopeAnnotation(), 45 | hasAnchor ? AnchorBean.class : null); 46 | return injector.getInstance(generatedType); 47 | } 48 | } 49 | 50 | /** 51 | * Override it to specify different annotation. By default, no annotation specified which will implicitly lead 52 | * to default prototype scope. 53 | * 54 | * @return scope annotation which should be applied to generated class 55 | */ 56 | protected Class getScopeAnnotation() { 57 | return null; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/field/AnnotatedFieldTypeListener.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.field; 2 | 3 | import com.google.inject.TypeLiteral; 4 | import com.google.inject.spi.InjectionListener; 5 | import com.google.inject.spi.TypeEncounter; 6 | import com.google.inject.spi.TypeListener; 7 | import ru.vyarus.guice.ext.core.util.Utils; 8 | 9 | import java.lang.annotation.Annotation; 10 | import java.lang.reflect.Field; 11 | 12 | /** 13 | * Generic type listener to process annotated fields after bean instantiation. 14 | * 15 | * @author Vyacheslav Rusakov 16 | * @since 30.06.2014 17 | * @param annotation type 18 | */ 19 | public class AnnotatedFieldTypeListener implements TypeListener { 20 | 21 | private final Class annotationClass; 22 | private final FieldPostProcessor postProcessor; 23 | 24 | public AnnotatedFieldTypeListener(final Class annotationClass, final FieldPostProcessor postProcessor) { 25 | this.annotationClass = annotationClass; 26 | this.postProcessor = postProcessor; 27 | } 28 | 29 | @Override 30 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 31 | public void hear(final TypeLiteral type, final TypeEncounter encounter) { 32 | final Class actualType = type.getRawType(); 33 | if (!Utils.isPackageValid(actualType)) { 34 | return; 35 | } 36 | Class investigatingType = actualType; 37 | while (investigatingType != null && !investigatingType.equals(Object.class)) { 38 | for (final Field field : investigatingType.getDeclaredFields()) { 39 | if (field.isAnnotationPresent(annotationClass)) { 40 | encounter.register(new InjectionListener() { 41 | @Override 42 | public void afterInjection(final I injectee) { 43 | try { 44 | field.setAccessible(true); 45 | postProcessor.process(field.getAnnotation(annotationClass), field, injectee); 46 | } catch (Exception ex) { 47 | throw new IllegalStateException( 48 | String.format("Failed to process annotation %s on field %s of class %s", 49 | annotationClass.getSimpleName(), field.getName(), 50 | injectee.getClass().getSimpleName()), ex); 51 | } 52 | } 53 | }); 54 | } 55 | } 56 | investigatingType = investigatingType.getSuperclass(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/method/AnnotatedMethodTypeListener.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.method; 2 | 3 | import com.google.inject.TypeLiteral; 4 | import com.google.inject.spi.InjectionListener; 5 | import com.google.inject.spi.TypeEncounter; 6 | import com.google.inject.spi.TypeListener; 7 | import ru.vyarus.guice.ext.core.util.Utils; 8 | 9 | import java.lang.annotation.Annotation; 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * Generic type listener to process annotated methods after bean instantiation. 14 | * 15 | * @author Vyacheslav Rusakov 16 | * @since 30.06.2014 17 | * @param annotation type 18 | */ 19 | public class AnnotatedMethodTypeListener implements TypeListener { 20 | 21 | private final Class annotationClass; 22 | private final MethodPostProcessor postProcessor; 23 | 24 | 25 | public AnnotatedMethodTypeListener(final Class annotationClass, 26 | final MethodPostProcessor postProcessor) { 27 | this.annotationClass = annotationClass; 28 | this.postProcessor = postProcessor; 29 | } 30 | 31 | @Override 32 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 33 | public void hear(final TypeLiteral type, final TypeEncounter encounter) { 34 | final Class actualType = type.getRawType(); 35 | if (!Utils.isPackageValid(actualType)) { 36 | return; 37 | } 38 | Class investigatingType = actualType; 39 | while (investigatingType != null && !investigatingType.equals(Object.class)) { 40 | for (final Method method : investigatingType.getDeclaredMethods()) { 41 | if (method.isAnnotationPresent(annotationClass)) { 42 | encounter.register(new InjectionListener() { 43 | @Override 44 | public void afterInjection(final I injectee) { 45 | try { 46 | method.setAccessible(true); 47 | postProcessor.process(method.getAnnotation(annotationClass), method, injectee); 48 | } catch (Exception ex) { 49 | throw new IllegalStateException( 50 | String.format("Failed to process annotation %s on method %s of class %s", 51 | annotationClass.getSimpleName(), method.getName(), 52 | injectee.getClass().getSimpleName()), ex); 53 | } 54 | } 55 | }); 56 | } 57 | } 58 | investigatingType = investigatingType.getSuperclass(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/ExtAnnotationsModule.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.matcher.Matcher; 5 | import com.google.inject.matcher.Matchers; 6 | import ru.vyarus.guice.ext.core.field.AnnotatedFieldTypeListener; 7 | import ru.vyarus.guice.ext.core.method.AnnotatedMethodTypeListener; 8 | import ru.vyarus.guice.ext.core.type.GeneralTypeListener; 9 | import ru.vyarus.guice.ext.core.util.ObjectPackageMatcher; 10 | import ru.vyarus.guice.ext.log.Log; 11 | import ru.vyarus.guice.ext.log.Slf4jLogAnnotationProcessor; 12 | import ru.vyarus.guice.ext.managed.DestroyableTypeProcessor; 13 | import ru.vyarus.guice.ext.managed.PostConstructAnnotationProcessor; 14 | import ru.vyarus.guice.ext.managed.PreDestroyAnnotationProcessor; 15 | import ru.vyarus.guice.ext.managed.destroyable.Destroyable; 16 | import ru.vyarus.guice.ext.managed.destroyable.DestroyableManager; 17 | 18 | import jakarta.annotation.PostConstruct; 19 | import jakarta.annotation.PreDestroy; 20 | 21 | /** 22 | * Additional annotations support module: @PostConstruct, @PreDestroy, @Log. 23 | * 24 | * @author Vyacheslav Rusakov 25 | * @since 29.06.2014 26 | */ 27 | @SuppressWarnings("checkstyle:ClassDataAbstractionCoupling") 28 | public class ExtAnnotationsModule extends AbstractModule { 29 | 30 | private Matcher typeMatcher; 31 | 32 | /** 33 | * Default module constructor to check annotations on all beans. 34 | */ 35 | public ExtAnnotationsModule() { 36 | this(Matchers.any()); 37 | } 38 | 39 | /** 40 | * Constructs annotation module with annotation scan limited to provided package. 41 | * (used mainly for startup performance optimization) 42 | * 43 | * @param pkg package to limit beans, where annotations processed 44 | */ 45 | public ExtAnnotationsModule(final String pkg) { 46 | this(new ObjectPackageMatcher<>(pkg)); 47 | } 48 | 49 | /** 50 | * Constructs annotation module with custom bean matcher for annotations processing. 51 | * 52 | * @param typeMatcher matcher to select beans for annotations processing 53 | */ 54 | public ExtAnnotationsModule(final Matcher typeMatcher) { 55 | this.typeMatcher = typeMatcher; 56 | } 57 | 58 | @Override 59 | protected void configure() { 60 | final DestroyableManager manager = configureManager(new DestroyableManager()); 61 | 62 | bindListener(typeMatcher, new GeneralTypeListener<>( 63 | Destroyable.class, new DestroyableTypeProcessor(manager))); 64 | 65 | bindListener(typeMatcher, new AnnotatedMethodTypeListener<>( 66 | PostConstruct.class, new PostConstructAnnotationProcessor())); 67 | 68 | bindListener(typeMatcher, new AnnotatedMethodTypeListener<>( 69 | PreDestroy.class, new PreDestroyAnnotationProcessor(manager))); 70 | 71 | bindListener(typeMatcher, new AnnotatedFieldTypeListener<>( 72 | Log.class, new Slf4jLogAnnotationProcessor())); 73 | } 74 | 75 | 76 | /** 77 | * Registers destroyable manager in injector and adds shutdown hook to process destroy on jvm shutdown. 78 | * 79 | * @param manager destroyable manager instance 80 | * @return manager instance 81 | */ 82 | protected DestroyableManager configureManager(final DestroyableManager manager) { 83 | bind(DestroyableManager.class).toInstance(manager); 84 | // if logic will not call destroy at least it will be called before jvm shutdown 85 | Runtime.getRuntime().addShutdownHook(new Thread(manager)); 86 | return manager; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/anchor/GeneratorAnchorModule.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator.anchor; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.internal.DynamicClassProvider; 5 | import com.google.inject.internal.DynamicSingletonProvider; 6 | 7 | import com.google.inject.Singleton; 8 | 9 | /** 10 | * Support module used to tie dynamic binding for generated class (generated with {@link DynamicClassProvider}) to 11 | * exact injector in injectors hierarchy. Also, allows using JIT resolution for generated classes in private modules 12 | * (without binding them explicitly). 13 | *

14 | * Situation without this module: suppose we have root and child injector and aop interceptors 15 | * (which must intercept generated abstract method calls) are registered in child module. If abstract bean 16 | * resolved with guice JIT ({@code @ProvidedBy(DynamicClassProvider.class)} then it's binding will be created 17 | * in upper-most injector: root injector. As a result, calling any abstract method will fail, because interceptors 18 | * are not present in root module (only in child module). 19 | *

20 | * To fix this problem we need to "tie" generated class binding to child injector. Guice will not bubble it up 21 | * only if it depends on some other bean located in child injector. For this purpose, module registers 22 | * dummy service {@link AnchorBean} which is only required to "hold" generated been in child injector. 23 | * Also, provider classes are explicitly bound to be able to operate with child injector in provider. 24 | * Both {@link DynamicClassProvider} and {@link DynamicSingletonProvider} will check first if anchor bean 25 | * is available in current injector and inject dependency on this bean into generated class (add new constructor 26 | * parameter for abstract class with constructor and generate new constructor for interface implementation or 27 | * abstract class without constructor). 28 | *

29 | * Also, module will prevent improper guice injector usage. For example, anchor module registered in child injector 30 | * and some service DynService depends (injects) on abstract class annotated 31 | * with {@code ProvidedBy(DynamicClassProvider.class)}. If DynService is not bound in child injector and we try 32 | * to create instance of (using JIT binding) it from root injector, then guice will try to obtain DynamicClassProvider 33 | * in root context and fail with duplicate binding definition (without anchor module, everything would pass, 34 | * but aop will not work, because it was registered in child injector). 35 | *

36 | * Example usage: 37 | *


38 |  *     Injector childInjector = Guice.createInjector() // root injector
39 |  *                              .createChildInjector(new GeneratorAnchorModule(), new MyAopModule());
40 |  *     // JIT binding for MyAbstractService (generated class) will be in child injector and so aop will work
41 |  *     childInjector.getInstance(MyAbstractService.class).someAMethodHandledWithAop();
42 |  * 
43 | *

44 | * Private module example: 45 | *


46 |  *     public class MyPrivateModule extends PrivateModule {
47 |  *         protected void configure() {
48 |  *             install(new MyAopModule());
49 |  *             install(new GeneratorAnchorModule());
50 |  *         }
51 |  *
52 |  *         {@literal @}Provides @Exposed @Named("myBean")
53 |  *         MyAbstractBean provide(MyAbstractBean bean) {
54 |  *             return bean;
55 |  *         }
56 |  *     }
57 |  *
58 |  *     Injector injector = Injector.createModule(new MyPrivateModule());
59 |  *     // obtain exposed named instance outside of private module
60 |  *     // note that abstract bean was not bound and resolved with JIT
61 |  *     injector.getInstance(Key.get(MyAbstractBean.class, Names.named("myBean")))
62 |  * 
63 | * 64 | * @author Vyacheslav Rusakov 65 | * @since 21.09.2016 66 | */ 67 | public class GeneratorAnchorModule extends AbstractModule { 68 | 69 | @Override 70 | protected void configure() { 71 | // providers bound explicitly to tie them to child injector 72 | bind(DynamicClassProvider.class); 73 | bind(DynamicSingletonProvider.class); 74 | // anchor bean used to tie dynamically provided beans to child injector 75 | bind(AnchorBean.class).in(Singleton.class); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/GeneratorAnchorsTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator; 2 | 3 | import com.google.inject.*; 4 | import com.google.inject.matcher.Matchers; 5 | import com.google.inject.name.Names; 6 | import org.aopalliance.intercept.MethodInterceptor; 7 | import org.aopalliance.intercept.MethodInvocation; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import ru.vyarus.guice.ext.core.generator.anchor.GeneratorAnchorModule; 11 | import ru.vyarus.guice.ext.generator.support.anchor.*; 12 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 13 | 14 | import com.google.inject.Inject; 15 | import com.google.inject.Provider; 16 | 17 | /** 18 | * @author Vyacheslav Rusakov 19 | * @since 21.09.2016 20 | */ 21 | public class GeneratorAnchorsTest { 22 | 23 | @Test(expected = AbstractMethodError.class) 24 | public void checkChildInjectorFailure() throws Exception { 25 | // aop registered in child injector 26 | Injector injector = Guice.createInjector().createChildInjector(new ChildAopModule()); 27 | // bean will be generated at root injector and so no aop will apply on it 28 | injector.getInstance(TestIface.class).hello(); 29 | } 30 | 31 | @Test 32 | public void testAnchorModule() throws Exception { 33 | // aop registered in child injector with anchor module 34 | Injector injector = Guice.createInjector().createChildInjector(new ChildAopModule(), new GeneratorAnchorModule()); 35 | injector.getInstance(TestIface.class).hello(); 36 | injector.getInstance(PureAbstractClass.class).hello(); 37 | // important: RootService not registered and so will be created by JIT in root injector 38 | injector.getInstance(CtorAbstractClass.class).hello(); 39 | injector.getInstance(GenericsCtorAbstractClass.class).hello(); 40 | } 41 | 42 | @Test 43 | public void dynamicInjectionTest() throws Exception { 44 | Injector injector = Guice.createInjector() 45 | .createChildInjector(new ChildAopModule(), new GeneratorAnchorModule()); 46 | // service resolved with jit still injects correct dependency 47 | injector.getInstance(DynamicService.class).hello(); 48 | } 49 | 50 | @Test 51 | public void testPrivateModules() throws Exception { 52 | Injector injector = Guice.createInjector(new AbstractModule() { 53 | @Override 54 | protected void configure() { 55 | install(new MyPrivateModule("test")); 56 | install(new MyPrivateModule("other")); 57 | } 58 | }); 59 | Assert.assertEquals(injector.getInstance(Key.get(TestIface.class, Names.named("test"))).hello(), "test"); 60 | Assert.assertEquals(injector.getInstance(Key.get(TestIface.class, Names.named("other"))).hello(), "other"); 61 | } 62 | 63 | static class ChildAopModule extends AbstractModule { 64 | 65 | String res; 66 | 67 | public ChildAopModule() { 68 | this(""); 69 | } 70 | 71 | public ChildAopModule(String res) { 72 | this.res = res; 73 | } 74 | 75 | @Override 76 | protected void configure() { 77 | bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new MethodInterceptor() { 78 | @Override 79 | public Object invoke(MethodInvocation invocation) throws Throwable { 80 | return res; 81 | } 82 | }); 83 | } 84 | } 85 | 86 | private static class MyPrivateModule extends PrivateModule { 87 | 88 | private String res; 89 | 90 | public MyPrivateModule(String res) { 91 | this.res = res; 92 | } 93 | 94 | @Override 95 | protected void configure() { 96 | install(new ChildAopModule(res)); 97 | install(new GeneratorAnchorModule()); 98 | 99 | // duplicate binding required for exposing (otherwise only provider method could be used) 100 | bind(TestIface.class).annotatedWith(Names.named(res)).toProvider(ExposedApi.class); 101 | 102 | expose(TestIface.class).annotatedWith(Names.named(res)); 103 | } 104 | 105 | // @Provides 106 | // @Exposed 107 | // @Named("test") 108 | // TestIface provide(TestIface serv) { 109 | // return serv; 110 | // } 111 | } 112 | 113 | private static class ExposedApi implements Provider { 114 | @Inject 115 | TestIface iface; 116 | 117 | @Override 118 | public TestIface get() { 119 | return iface; 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/AnnotationMemberValueVisitor.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import javassist.ClassPool; 5 | import javassist.CtClass; 6 | import javassist.bytecode.ConstPool; 7 | import javassist.bytecode.annotation.*; 8 | 9 | import java.lang.reflect.Array; 10 | import java.lang.reflect.Method; 11 | import java.util.Map; 12 | 13 | /** 14 | * Sets annotation method value. 15 | *

Based on code from tapestry 5.

16 | * 17 | * @author Vyacheslav Rusakov 18 | * @since 08.12.2014 19 | */ 20 | @SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "PMD.AvoidUsingShortType"}) 21 | public class AnnotationMemberValueVisitor implements MemberValueVisitor { 22 | 23 | private static final Map, CtClass> PRIMITIVES = ImmutableMap., CtClass>builder() 24 | .put(boolean.class, CtClass.booleanType) 25 | .put(Boolean.class, CtClass.booleanType) 26 | .put(byte.class, CtClass.byteType) 27 | .put(Byte.class, CtClass.byteType) 28 | .put(char.class, CtClass.charType) 29 | .put(Character.class, CtClass.charType) 30 | .put(short.class, CtClass.shortType) 31 | .put(Short.class, CtClass.shortType) 32 | .put(int.class, CtClass.intType) 33 | .put(Integer.class, CtClass.intType) 34 | .put(long.class, CtClass.longType) 35 | .put(Long.class, CtClass.longType) 36 | .put(float.class, CtClass.floatType) 37 | .put(Float.class, CtClass.floatType) 38 | .put(double.class, CtClass.doubleType) 39 | .put(Double.class, CtClass.doubleType) 40 | .build(); 41 | 42 | private final ClassPool classPool; 43 | private final ConstPool constPool; 44 | private final Object value; 45 | 46 | public AnnotationMemberValueVisitor(final ClassPool classPool, final ConstPool constPool, final Object value) { 47 | this.classPool = classPool; 48 | this.constPool = constPool; 49 | this.value = value; 50 | } 51 | 52 | @Override 53 | public void visitAnnotationMemberValue(final AnnotationMemberValue mb) { 54 | final Class annotationType = getClass(value); 55 | final Method[] methods = annotationType.getDeclaredMethods(); 56 | 57 | try { 58 | for (final Method method : methods) { 59 | final Object result = method.invoke(value); 60 | mb.getValue().addMemberValue(method.getName(), createValue(result)); 61 | 62 | } 63 | } catch (Exception e) { 64 | throw new RuntimeException("Failed to copy annotation value", e); 65 | } 66 | } 67 | 68 | @Override 69 | public void visitArrayMemberValue(final ArrayMemberValue mb) { 70 | final int length = Array.getLength(this.value); 71 | final MemberValue[] members = new MemberValue[length]; 72 | try { 73 | for (int i = 0; i < length; i++) { 74 | final Object object = Array.get(this.value, i); 75 | members[i] = createValue(object); 76 | } 77 | mb.setValue(members); 78 | } catch (final Exception e) { 79 | throw new RuntimeException("Failed to copy array value", e); 80 | } 81 | } 82 | 83 | @Override 84 | public void visitBooleanMemberValue(final BooleanMemberValue mb) { 85 | mb.setValue((Boolean) this.value); 86 | } 87 | 88 | @Override 89 | public void visitByteMemberValue(final ByteMemberValue mb) { 90 | mb.setValue((Byte) this.value); 91 | } 92 | 93 | @Override 94 | public void visitCharMemberValue(final CharMemberValue mb) { 95 | mb.setValue((Character) this.value); 96 | } 97 | 98 | @Override 99 | public void visitDoubleMemberValue(final DoubleMemberValue mb) { 100 | mb.setValue((Double) this.value); 101 | } 102 | 103 | @Override 104 | public void visitEnumMemberValue(final EnumMemberValue mb) { 105 | final Enum enumeration = (Enum) this.value; 106 | final Class type = enumeration.getDeclaringClass(); 107 | mb.setType(type.getName()); 108 | mb.setValue(enumeration.name()); 109 | } 110 | 111 | @Override 112 | public void visitFloatMemberValue(final FloatMemberValue mb) { 113 | mb.setValue((Float) this.value); 114 | } 115 | 116 | @Override 117 | public void visitIntegerMemberValue(final IntegerMemberValue mb) { 118 | mb.setValue((Integer) this.value); 119 | } 120 | 121 | @Override 122 | public void visitLongMemberValue(final LongMemberValue mb) { 123 | mb.setValue((Long) this.value); 124 | } 125 | 126 | @Override 127 | public void visitShortMemberValue(final ShortMemberValue mb) { 128 | mb.setValue((Short) this.value); 129 | } 130 | 131 | @Override 132 | public void visitStringMemberValue(final StringMemberValue mb) { 133 | mb.setValue((String) this.value); 134 | } 135 | 136 | @Override 137 | public void visitClassMemberValue(final ClassMemberValue mb) { 138 | mb.setValue(((Class) this.value).getName()); 139 | } 140 | 141 | private MemberValue createValue(final Object value) throws Exception { 142 | final MemberValue memberValue = Annotation.createMemberValue( 143 | this.constPool, getCtClass(classPool, getClass(value))); 144 | memberValue.accept(new AnnotationMemberValueVisitor(this.classPool, this.constPool, value)); 145 | return memberValue; 146 | } 147 | 148 | private Class getClass(final Object object) { 149 | final boolean isAnnotation = object instanceof java.lang.annotation.Annotation; 150 | return isAnnotation ? ((java.lang.annotation.Annotation) object).annotationType() : object.getClass(); 151 | } 152 | 153 | private static CtClass getCtClass(final ClassPool pool, final Class type) throws Exception { 154 | return PRIMITIVES.containsKey(type) 155 | ? PRIMITIVES.get(type) 156 | : pool.get(type.getName()); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /src/test/java/ru/vyarus/guice/ext/generator/GeneratorTest.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.generator; 2 | 3 | import com.google.inject.*; 4 | import com.google.inject.matcher.Matchers; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import ru.vyarus.guice.ext.core.generator.DynamicClassException; 8 | import ru.vyarus.guice.ext.core.generator.DynamicClassGenerator; 9 | import ru.vyarus.guice.ext.generator.support.*; 10 | import ru.vyarus.guice.ext.generator.support.aop.CustomAop; 11 | import ru.vyarus.guice.ext.generator.support.aop.CustomAopInterceptor; 12 | import ru.vyarus.guice.ext.generator.support.bad.BadDeclarationBean; 13 | import ru.vyarus.guice.ext.generator.support.bad.BadSingletonDeclaration; 14 | import ru.vyarus.guice.ext.generator.support.bad.WrongUsageBean; 15 | import ru.vyarus.guice.ext.generator.support.composite.CompositeCase1; 16 | import ru.vyarus.guice.ext.generator.support.composite.CompositeCase2; 17 | import ru.vyarus.guice.ext.generator.support.ctor.Ann; 18 | import ru.vyarus.guice.ext.generator.support.ctor.CustomConstructorBean; 19 | import ru.vyarus.guice.ext.generator.support.ctor.GenerifiedConstructorBean; 20 | 21 | import com.google.inject.Singleton; 22 | import java.lang.annotation.Annotation; 23 | import java.lang.annotation.ElementType; 24 | import java.lang.reflect.Constructor; 25 | import java.util.Arrays; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertTrue; 29 | 30 | /** 31 | * @author Vyacheslav Rusakov 32 | * @since 10.12.2014 33 | */ 34 | public class GeneratorTest { 35 | 36 | @Test 37 | public void testClassGenerationCached() throws Exception { 38 | Class cl1 = DynamicClassGenerator.generate(InterfaceBean.class); 39 | // this time class should not be generated 40 | Class cl2 = DynamicClassGenerator.generate(InterfaceBean.class); 41 | assertTrue(cl1 == cl2); 42 | } 43 | 44 | @Test 45 | public void testDirectRegistration() throws Exception { 46 | Injector injector = Guice.createInjector(new AbstractModule() { 47 | @Override 48 | protected void configure() { 49 | bind(InterfaceBean.class).to(DynamicClassGenerator.generate(InterfaceBean.class)); 50 | bind(AbstractBean.class).to(DynamicClassGenerator.generate(AbstractBean.class)); 51 | } 52 | }); 53 | injector.getInstance(InterfaceBean.class); 54 | injector.getInstance(AbstractBean.class); 55 | } 56 | 57 | @Test 58 | public void testIndirectRegistration() throws Exception { 59 | Injector injector = Guice.createInjector(new AbstractModule() { 60 | @SuppressWarnings("PointlessBinding") 61 | @Override 62 | protected void configure() { 63 | bind(ProvidedInterfaceBean.class); 64 | bind(ProvidedAbstractBean.class); 65 | } 66 | }); 67 | injector.getInstance(ProvidedInterfaceBean.class); 68 | injector.getInstance(ProvidedAbstractBean.class); 69 | } 70 | 71 | @Test 72 | public void testJITResolution() throws Exception { 73 | Injector injector = Guice.createInjector(); 74 | injector.getInstance(ProvidedInterfaceBean.class); 75 | injector.getInstance(ProvidedAbstractBean.class); 76 | } 77 | 78 | @Test 79 | public void testAnnotationPropagation() throws Exception { 80 | Injector injector = Guice.createInjector(); 81 | ProvidedInterfaceBean interfaceInstance = injector.getInstance(ProvidedInterfaceBean.class); 82 | ProvidedAbstractBean beanInstance = injector.getInstance(ProvidedAbstractBean.class); 83 | // singleton scope declared on interface and propagated 84 | assertEquals(interfaceInstance, injector.getInstance(ProvidedInterfaceBean.class)); 85 | // no scope definition - prototype 86 | Assert.assertNotEquals(beanInstance, injector.getInstance(ProvidedAbstractBean.class)); 87 | } 88 | 89 | @Test 90 | public void testAnnotationCopyCorrectness() throws Exception { 91 | Injector injector = Guice.createInjector(new AbstractModule() { 92 | @Override 93 | protected void configure() { 94 | bind(InterfaceBean.class).to(DynamicClassGenerator.generate(InterfaceBean.class)); 95 | } 96 | }); 97 | InterfaceBean bean = injector.getInstance(InterfaceBean.class); 98 | AnnotationCheck ann = bean.getClass().getAnnotation(AnnotationCheck.class); 99 | assertTrue(ann.ann().value() == Singleton.class); 100 | assertTrue(Arrays.equals(ann.arr(), new int[]{1, 2, 3})); 101 | assertTrue(Arrays.equals(ann.arrBool(), new boolean[]{true, false})); 102 | assertTrue(Arrays.equals(ann.arrByte(), new byte[]{1, 2})); 103 | assertTrue(Arrays.equals(ann.arrChar(), new char[]{',', 'l'})); 104 | assertTrue(Arrays.equals(ann.arrDbl(), new double[]{0.1, 0.2})); 105 | assertTrue(Arrays.equals(ann.arrFlt(), new float[]{0.1f, 0.2f})); 106 | assertTrue(Arrays.equals(ann.arrLng(), new long[]{1l, 2l})); 107 | assertTrue(Arrays.equals(ann.arrObj(), new String[]{"tst", "tst2"})); 108 | assertTrue(ann.bool()); 109 | assertTrue(ann.bte() == 1); 110 | assertTrue(ann.chr() == ','); 111 | assertTrue(ann.dbl() == 1d); 112 | assertTrue(ann.integer() == 1); 113 | assertTrue(ann.lng() == 1l); 114 | assertTrue(ann.flt() == 1f); 115 | assertTrue(ann.srt() == 1); 116 | assertTrue(ann.str().equals("string")); 117 | assertTrue(ann.cls() == InterfaceBean.class); 118 | assertTrue(ann.enm() == ElementType.TYPE); 119 | } 120 | 121 | @Test(expected = DynamicClassException.class) 122 | public void testDirectScopeAnnotation() throws Exception { 123 | // scope annotation can't be used directly 124 | DynamicClassGenerator.generate(BadDeclarationBean.class); 125 | } 126 | 127 | @Test 128 | public void testAop() throws Exception { 129 | Injector injector = Guice.createInjector(new AbstractModule() { 130 | @Override 131 | protected void configure() { 132 | bind(InterfaceBean.class).to(DynamicClassGenerator.generate(InterfaceBean.class)); 133 | bind(AbstractBean.class).to(DynamicClassGenerator.generate(AbstractBean.class)); 134 | bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor()); 135 | } 136 | }); 137 | injector.getInstance(InterfaceBean.class).hello(); 138 | injector.getInstance(AbstractBean.class).hello(); 139 | injector.getInstance(ProvidedInterfaceBean.class).hello(); 140 | injector.getInstance(ProvidedAbstractBean.class).hello(); 141 | } 142 | 143 | @Test(expected = AbstractMethodError.class) 144 | public void testNoAopCall() throws Exception { 145 | Guice.createInjector().getInstance(ProvidedInterfaceBean.class).badMethod(); 146 | } 147 | 148 | @Test 149 | public void testCompositeCases() throws Exception { 150 | Injector injector = Guice.createInjector(new AbstractModule() { 151 | @Override 152 | protected void configure() { 153 | bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor()); 154 | } 155 | }); 156 | CompositeCase1 case1 = injector.getInstance(CompositeCase1.class); 157 | CompositeCase2 case2 = injector.getInstance(CompositeCase2.class); 158 | 159 | case1.self(); 160 | case1.hello(); 161 | 162 | case2.self(); 163 | case2.hello(); 164 | } 165 | 166 | @Test(expected = ProvisionException.class) 167 | public void testWrongProviderUsage() throws Exception { 168 | Guice.createInjector().getInstance(WrongUsageBean.class); 169 | } 170 | 171 | @Test 172 | @SuppressWarnings("unchecked") 173 | public void testCustomConstructor() throws Exception { 174 | // original type constructor copied and constructor injection correctly handled 175 | Guice.createInjector(new AbstractModule() { 176 | @Override 177 | protected void configure() { 178 | bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor()); 179 | } 180 | }).getInstance(CustomConstructorBean.class).hello(); 181 | Class generated = DynamicClassGenerator.generate(CustomConstructorBean.class); 182 | assertEquals(1, generated.getConstructors().length); 183 | Constructor ctor = generated.getConstructors()[0]; 184 | assertTrue(ctor.isAnnotationPresent(Inject.class)); 185 | assertTrue(ctor.isAnnotationPresent(Ann.class)); 186 | assertEquals("ctor", ((Ann) ctor.getAnnotation(Ann.class)).value()); 187 | assertEquals(1, ctor.getExceptionTypes().length); 188 | assertEquals(1, ctor.getParameterAnnotations()[0].length); 189 | Annotation ann = ctor.getParameterAnnotations()[0][0]; 190 | assertTrue(ann instanceof Ann); 191 | assertEquals("param", ((Ann) ann).value()); 192 | } 193 | 194 | @Test 195 | public void testGenerifiedConstructor() throws Exception { 196 | // original type constructor copied and constructor injection correctly handled 197 | Guice.createInjector(new AbstractModule() { 198 | @Override 199 | protected void configure() { 200 | bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor()); 201 | } 202 | }).getInstance(GenerifiedConstructorBean.class).hello(); 203 | } 204 | 205 | @Test 206 | public void testSingletonProvider() throws Exception { 207 | Injector injector = Guice.createInjector(new AbstractModule() { 208 | @Override 209 | protected void configure() { 210 | bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor()); 211 | } 212 | }); 213 | GenerifiedConstructorBean bean1 = injector.getInstance(GenerifiedConstructorBean.class); 214 | GenerifiedConstructorBean bean2 = injector.getInstance(GenerifiedConstructorBean.class); 215 | Assert.assertTrue(bean1 == bean2); 216 | } 217 | 218 | @Test(expected = ProvisionException.class) 219 | public void testDuplicateSingletonDeclaration() throws Exception { 220 | Guice.createInjector().getInstance(BadSingletonDeclaration.class); 221 | 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guice annotations extensions 2 | [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://www.opensource.org/licenses/MIT) 3 | [![CI](https://github.com/xvik/guice-ext-annotations/actions/workflows/CI.yml/badge.svg)](https://github.com/xvik/guice-ext-annotations/actions/workflows/CI.yml) 4 | [![Appveyor build status](https://ci.appveyor.com/api/projects/status/github/xvik/guice-ext-annotations?svg=true)](https://ci.appveyor.com/project/xvik/guice-ext-annotations) 5 | [![codecov](https://codecov.io/gh/xvik/guice-ext-annotations/branch/master/graph/badge.svg)](https://codecov.io/gh/xvik/guice-ext-annotations) 6 | 7 | Support: [Gitter chat](https://gitter.im/xvik/guice-ext-annotations) 8 | 9 | ### About 10 | 11 | Guice annotations support extensions. 12 | 13 | Features: 14 | * Allows using interfaces and abstract classes as guice beans: abstract methods must be handled with guice aop 15 | * Additional annotations: 16 | * @Log: auto inject slf4j logger 17 | * JSR-250 @PostConstruct: annotated method called after bean initialization 18 | * JSR-250 @PreDestroy: annotated method called before shutdown 19 | * Simplifies [TypeListener](http://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/spi/TypeListener.html) api for cases: 20 | * Add custom field annotation support 21 | * Add custom method annotation support 22 | * Add post processing for beans of some type (e.g. implementing interface or extending some abstract class) 23 | 24 | ### Thanks to 25 | 26 | * [Derric Gilling](https://github.com/dgilling) for help with playframework related issues 27 | 28 | ### Setup 29 | 30 | [![Maven Central](https://img.shields.io/maven-central/v/ru.vyarus/guice-ext-annotations.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/ru.vyarus/guice-ext-annotations) 31 | 32 | Maven: 33 | 34 | ```xml 35 | 36 | ru.vyarus 37 | guice-ext-annotations 38 | 2.0.1 39 | 40 | ``` 41 | 42 | Gradle: 43 | 44 | ```groovy 45 | implementation 'ru.vyarus:guice-ext-annotations:2.0.1' 46 | ``` 47 | 48 | **NOTE**: version 2.0 compatible with jakarta.annotation-api 2 (jakarta.annotation namespace for `@PostConstruct` 49 | and `@PreDestroy` annotations), but guice still use `javax.inject` namespace. 50 | 51 | For `javax.annotation` use [version 1.4.1](https://github.com/xvik/guice-ext-annotations/tree/1.x) 52 | 53 | ### Abstract types support 54 | 55 | Feature was developed to support repositories in [guice-persist-orient](https://github.com/xvik/guice-persist-orient#defining-repository) 56 | (look usage examples). 57 | 58 | #### Problem 59 | 60 | Suppose you have annotation to simplify sql query definition: 61 | 62 | ```java 63 | @Query("select from Model") 64 | List list() { 65 | throw new UnsupportedOperationException(); 66 | } 67 | ``` 68 | 69 | Support for such annotation could be easily implemented with [guice aop](https://github.com/google/guice/wiki/AOP). 70 | But we will always have to declare method body. 71 | 72 | To avoid this, [guice-persist](https://github.com/google/guice/wiki/GuicePersist) creates JDK proxies from interfaces with annotated methods. 73 | It solves problem with redundant method body, but did not allow using guice aop on such beans. 74 | 75 | #### Solution 76 | 77 | Guice needs to control bean instance creation to properly apply aop, so solution is simple: dynamically create 78 | implementation class from abstract class or interface, and let guice instantiate bean from it. 79 | 80 | Additional actions during class generation: 81 | * Annotations copied from abstract type (class or interface) to allow aop correctly resolve them 82 | * If abstract bean use constructor injection, the same constructor will be created in implementation (including all 83 | constructor and parameters annotations and generic signatures). 84 | 85 | All this allows thinking of abstract type as of usual guice bean. 86 | 87 | #### Setup 88 | 89 | In order to use dynamic proxies, add dependency on javassist library: 90 | 91 | ```groovy 92 | implementation 'org.javassist:javassist:3.28.0-GA' 93 | ``` 94 | 95 | Minimal required version is 3.24. 96 | 97 | NOTE: javassist used instead of cglib, because cglib can't manipulate annotations. 98 | 99 | #### Usage 100 | 101 | There are two options: declare bean directly in module or rely on JIT based declaration. 102 | 103 | For first option, generate class in your guice module: 104 | 105 | ```java 106 | bind(MyAbstractType.class).to(DynamicClassGenerator.generate(MyAbstractType.class)); 107 | ``` 108 | 109 | If you don't want to declare manually and prefer minimal configuration with 110 | [JIT](https://github.com/google/guice/wiki/JustInTimeBindings) bindings, use `@ProvidedBy`: 111 | 112 | ```java 113 | @ProvidedBy(DynamicClassProvider.class) 114 | public interface MyAbstractType {...} 115 | ``` 116 | 117 | (the same for abstract class) 118 | 119 | After all, bean could be injected as usual: 120 | 121 | ```java 122 | @Inject MyAbstractType myBean; 123 | ``` 124 | 125 | Don't forget that all abstract methods must be handled with aop: otherwise you will get abstract method call exception. 126 | 127 | #### Class loaders 128 | 129 | May be used within complex classloader hierarchies (like playframework dev mode). 130 | 131 | Class loader of abstract type is used to generate implementation class. Generated class is first checked fo existence in this 132 | class loader and, if not found, generated and attached to that class loader. For example, if the same class will be loaded by different 133 | class loaders, then generator will generate different implementations. 134 | In play dev mode this will mean that after hot reload (classloader with your abstract class replace) generator will 135 | generate new implementation for new (updated) abstract class. 136 | 137 | Class generation is thread safe: synchronized on abstract type to allow concurrent generation for different classes and prevent 138 | duplicate generations. 139 | 140 | #### Limitation 141 | 142 | There is only one limitation: you can't use scope annotations directly on abstract types - guice doesn't allow it. 143 | To workaround it use wrapper annotation: 144 | 145 | ```java 146 | @ScopeAnnotation(Singleton.class) 147 | @ProvidedBy(DynamicClassProvider.class) 148 | public interface MyAbstractType {...} 149 | ``` 150 | 151 | NOTE: yes, annotation named the same as guice's own annotation, but name is so good and they will never met in one class. 152 | 153 | #### Singletons 154 | 155 | Singletons are pretty common case. To simplify singletons definition special provider could be used: 156 | 157 | ```java 158 | @ProvidedBy(DynamicSingletonProvider.class) 159 | public interface MyAbstractType {...} 160 | ``` 161 | 162 | This is completely equivalent to code in limitations section, but requires just one annotation. 163 | 164 | If, by accident, singleton provider will be used with `@ScopeAnnotation`, error will be thrown. 165 | 166 | #### Child injectors and private modules 167 | 168 | When JIT (dynamic) resolution is used (bindings not described in module) then, during dynamic binding creation, 169 | guice will try to create binding in upper most injector (in order to re-use instance in possibly other 170 | child injectors (java class loaders works the same way by the same reason)). 171 | 172 | For example, suppose there is some root injector and your module with aop (used to handle annotations on abstract classes) 173 | is declared in child injector. 174 | 175 | ```java 176 | Guice.createInjector().createChildInjector(new MyAopModule()); 177 | ``` 178 | 179 | Some abstract type without explicit binding: 180 | 181 | ```java 182 | @ProvidedBy(DynamicClassProvider.class) 183 | public interface MyAbstractBean {} 184 | ``` 185 | 186 | If some service depends on this abstract type (injects it), then JIT binding for MyAbstractBean will be created 187 | in root(!) module. But aop to handle your custom annotations is declared in child module and so you will get 188 | abstract method call exception when try to call any method of abstract bean. 189 | 190 | For example, such case could appear with test-ng guice integration, which always start your modules as child injector. 191 | 192 | In general, there are two ways to workaround such situation: 193 | * Manually declare all required bindings in child module 194 | * Abstract type must depend on some other bean in child injector (this will force JIT binding to be created in child module (prevent bubbling up)) 195 | 196 | ##### Solution 197 | 198 | Out of the box, special module is available to solve this problem: `GeneratorAnchorModule`. 199 | 200 | If it will be used in example above, then JIT bindings for abstract types will be created in child injector automatically (fixing behaviour): 201 | 202 | ```java 203 | Guice.createInjector().createChildInjector(new MyAopModule(), new GeneratorAnchorModule()); 204 | ``` 205 | 206 | Module use "anchor" technic: dummy bean (AnchorBean) is registered in child injector and all dynamically generated classes 207 | (when `@ProvidedBy` used) become dependent of this dummy bean (AnchorBean added to generated implementation class constructor). 208 | 209 | Also, module will allow using dynamic bindings inside private module: 210 | 211 | ```java 212 | public class MyPrivateModule extends PrivateModule { 213 | protected void configure() { 214 | install(new MyAopModule()); 215 | install(new GeneratorAnchorModule()); 216 | } 217 | 218 | @Provides 219 | @Exposed 220 | @Named("myBean") 221 | MyAbstractBean provide(MyAbstractBean bean) { 222 | return bean; 223 | } 224 | } 225 | 226 | Injector injector = Injector.createModule(new MyPrivateModule()); 227 | injector.getInstance(Key.get(MyAbstractBean.class, Names.named("myBean"))) 228 | ``` 229 | 230 | Note that MyAbstractBean is not bound explicitly, but still correct instance exposed from private module. 231 | 232 | ### Additional annotations 233 | 234 | Guice module adds three annotations support (`@Log`, `@PostConstruct`, `@PreDestroy`) and `Destroyable` types. 235 | 236 | #### Install the Guice module 237 | 238 | ```java 239 | install(new ExtAnnotationsModule()); 240 | ``` 241 | 242 | To limit processed beans to specific package use: 243 | 244 | ```java 245 | install(new ExtAnnotationsModule("your.package")); 246 | ``` 247 | 248 | Alternatively custom object matcher may be used to reduce processed beans: 249 | 250 | ```java 251 | install(new ExtAnnotationsModule(new YourCustomMatcher())); 252 | ``` 253 | 254 | #### Usage 255 | 256 | ##### @Log 257 | 258 | Annotate logger filed: 259 | 260 | ```java 261 | @Log 262 | private org.slf4j.Logger logger 263 | ``` 264 | 265 | Only Slf4j logger supported. Trying to use annotation with other logger will throw exception on initialization. 266 | 267 | ##### @PostConstruct 268 | 269 | Annotate bean method to be called after bean initialization. 270 | 271 | ```java 272 | @PostConstruct 273 | private void init() { 274 | // init logic 275 | } 276 | ``` 277 | 278 | Annotated method must not contain parameters or exception will be thrown on initialization. 279 | 280 | ##### @PreDestroy 281 | 282 | Annotate bean method to be called before shutdown (by default before jvm shutdown). 283 | 284 | ```java 285 | @PreDestroy 286 | private void destroy() { 287 | // destroy logic 288 | } 289 | ``` 290 | 291 | Annotated method must not contain parameters or exception will be thrown on initialization. 292 | 293 | Destroy processing may be triggered manually by: 294 | 295 | ```java 296 | injector.getBean(ru.vyarus.guice.ext.managed.destroyable.DestroyableManager.class).destroy() 297 | ``` 298 | Useful if guice used in conjunction with some other container (e.g. web container). Destroy may be called any number of times, 299 | but actual destroy will be processed only on first execution. 300 | 301 | ##### Destroyable 302 | 303 | As an alternative to `@PreDestroy`, bean may implement `ru.vyarus.guice.ext.managed.destroyable.Destroyable` 304 | 305 | ```java 306 | public class MyBean implements Destroyable 307 | ``` 308 | 309 | ### Additional api 310 | 311 | Api simplifies work with [TypeListener](http://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/spi/TypeListener.html). 312 | 313 | ##### Custom field annotation post processor 314 | 315 | Implement `ru.vyarus.guice.ext.core.field.FieldPostProcessor`. 316 | Handle only recoverable exceptions, otherwise it will be handled by `AnnotatedFieldTypeListener`. 317 | 318 | For example: 319 | 320 | ```java 321 | public class Slf4jLogAnnotationProcessor implements FieldPostProcessor { 322 | 323 | @Override 324 | public void process(final Log annotation, final Field field, final Object instance) throws Exception { 325 | final Logger logger = LoggerFactory.getLogger(field.getDeclaringClass()); 326 | field.set(instance, logger); 327 | } 328 | } 329 | ``` 330 | 331 | Register in module: 332 | 333 | ```java 334 | bindListener(typeMatcher, new AnnotatedFieldTypeListener( 335 | Log.class, new Slf4jLogAnnotationProcessor())); 336 | ``` 337 | 338 | ##### Custom method annotation post processor 339 | 340 | Implement `ru.vyarus.guice.ext.core.method.MethodPostProcessor`. 341 | Handle only recoverable exceptions, otherwise it will be handled by `AnnotatedMethodTypeListener`. 342 | 343 | For example: 344 | 345 | ```java 346 | public class PostConstructAnnotationProcessor implements MethodPostProcessor { 347 | 348 | @Override 349 | public void process(final PostConstruct annotation, final Method method, final Object instance) throws Exception { 350 | Utils.checkNoParams(method); 351 | method.invoke(instance); 352 | } 353 | } 354 | ``` 355 | 356 | Register in module: 357 | 358 | ```java 359 | bindListener(typeMatcher, new AnnotatedMethodTypeListener( 360 | PostConstruct.class, new PostConstructAnnotationProcessor())); 361 | ``` 362 | 363 | ##### Custom type post processor 364 | 365 | Implement `ru.vyarus.guice.ext.core.type.TypePostProcessor`. 366 | Handle only recoverable exceptions, otherwise it will be handled by `GeneralTypeListener`. 367 | 368 | For example: 369 | 370 | ```java 371 | public class DestroyableTypeProcessor implements TypePostProcessor { 372 | private DestroyableManager manager; 373 | 374 | public DestroyableTypeProcessor(final DestroyableManager manager) { 375 | this.manager = manager; 376 | } 377 | 378 | @Override 379 | public void process(final Destroyable instance) { 380 | manager.register(instance); 381 | } 382 | } 383 | ``` 384 | 385 | Register in module: 386 | 387 | ```java 388 | bindListener(typeMatcher, new GeneralTypeListener( 389 | Destroyable.class, new DestroyableTypeProcessor(manager))); 390 | ``` 391 | 392 | `GeneralTypeListener` match bean by exact type, super type or interface (direct or super type interface). 393 | 394 | Example of [post processing beans extending abstract class](https://github.com/xvik/guice-ext-annotations/blob/master/src/test/java/ru/vyarus/guice/ext/postprocess/TypePostProcessorTest.java) 395 | 396 | Listener binding may seem to be more complicated than it could: typeMatcher could filter types instead of custom logic. 397 | This was done on purpose - to allow using mather for appliance scoping. For example, if only beans from exact packages 398 | should be processed, or any other conditions. 399 | 400 | --- 401 | [![java lib generator](http://img.shields.io/badge/Powered%20by-%20Java%20lib%20generator-green.svg?style=flat-square)](https://github.com/xvik/generator-lib-java) 402 | -------------------------------------------------------------------------------- /src/main/java/ru/vyarus/guice/ext/core/generator/DynamicClassGenerator.java: -------------------------------------------------------------------------------- 1 | package ru.vyarus.guice.ext.core.generator; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.inject.ImplementedBy; 5 | import com.google.inject.Inject; 6 | import com.google.inject.ProvidedBy; 7 | import com.google.inject.internal.Annotations; 8 | import javassist.*; 9 | import javassist.bytecode.*; 10 | import javassist.bytecode.annotation.Annotation; 11 | import ru.vyarus.guice.ext.core.util.Utils; 12 | 13 | import java.lang.reflect.AnnotatedElement; 14 | import java.lang.reflect.Constructor; 15 | import java.lang.reflect.Modifier; 16 | 17 | /** 18 | * Dynamically generates new class from abstract class or interface. 19 | * Resulted class may be used as implementation in guice. 20 | * Generated class will be tied to class loader of original type. 21 | * It is safe to to use with dynamic class loaders (like play dev mode). 22 | *

Guice will be able to apply aop to generated class methods. Annotations are copied from original class, 23 | * to let aop mechanisms work even for interfaces. As a result you may forget about class generation and think 24 | * of abstract class or interface as usual guice bean.

25 | *

Abstract methods will not be implemented: abstract method call error will be thrown if you try to call it. 26 | * All abstract methods must be covered by guice aop.

27 | *

It may be used directly in guice module to register interface or abstract class: 28 | * {@code bind(MyType.class).to(DynamicClassGenerator.generate(MyType.class))}. 29 | * Another option is to use {@link com.google.inject.internal.DynamicClassProvider}: annotate type with 30 | * {@code @ProvidedBy(DynamicClassProvider.class)} and either rely on JIT (don't register type at all) or 31 | * simply register type: @{code bind(MyType.class)}. 32 | * If used with injectors hierarchy or within private modules, use "anchor" dependency to prevent bubbling up 33 | * for resulted binding (see for example anchor implementation for use with {@code @ProvidedBy} annotation 34 | * {@link ru.vyarus.guice.ext.core.generator.anchor.GeneratorAnchorModule}).

35 | *

Don't use scope annotations directly - instead wrap them into 36 | * {@link ru.vyarus.guice.ext.core.generator.ScopeAnnotation}, because guice doesn't allow scope definition on 37 | * abstract types.

38 | * 39 | * @author Vyacheslav Rusakov 40 | * @see com.google.inject.internal.DynamicClassProvider 41 | * @since 10.12.2014 42 | */ 43 | @SuppressWarnings({"checkstyle:ClassDataAbstractionCoupling", "checkstyle:GodClass", "PMD.GodClass"}) 44 | public final class DynamicClassGenerator { 45 | 46 | /** 47 | * Postfix applied to interface or abstract class name to get generated class name. 48 | */ 49 | public static final String DYNAMIC_CLASS_POSTFIX = "$GuiceDynamicClass"; 50 | public static final String JAVAX_INJECT = "javax.inject.Inject"; 51 | public static final String JAKARTA_INJECT = "jakarta.inject.Inject"; 52 | 53 | private DynamicClassGenerator() { 54 | } 55 | 56 | /** 57 | * Shortcut for {@link #generate(Class, Class, Class)} method to create default scoped classes. 58 | *

59 | * Method is thread safe. 60 | * 61 | * @param type interface or abstract class 62 | * @param type 63 | * @return implementation class for provided type (will not generate if class already exist) 64 | */ 65 | public static Class generate(final Class type) { 66 | return generate(type, null); 67 | } 68 | 69 | /** 70 | * Shortcut for {@link #generate(Class, Class, Class)} method to create classes with provided scope 71 | * (and without extra anchor). 72 | *

73 | * Method is thread safe. 74 | * 75 | * @param type interface or abstract class 76 | * @param scope scope annotation to apply on generated class (may be null for default prototype scope) 77 | * @param type 78 | * @return implementation class for provided type (will not generate if class already exist) 79 | */ 80 | public static Class generate(final Class type, 81 | final Class scope) { 82 | return generate(type, scope, null); 83 | } 84 | 85 | /** 86 | * Generates dynamic class, which guice may use as implementation and generate proxy above it, 87 | * correctly applying aop features. 88 | *

89 | * New class will inherit type annotations and constructor with annotations 90 | * (if base class use constructor injection). Also constructor inherits all annotations, including 91 | * parameters annotations. If anchor is provided then it will be added as last constructor parameter 92 | * or (when abstract type has no constructor) new constructor added with one parameter (anchor). 93 | *

94 | * Method is thread safe. 95 | * 96 | * @param type interface or abstract class 97 | * @param scope scope annotation to apply on generated class (may be null for default prototype scope) 98 | * @param anchor existing binding to depend generated class on (to prevent binding bubbling up to root injector) 99 | * @param type 100 | * @return implementation class for provided type (will not generate if class already exist) 101 | * @see ru.vyarus.guice.ext.core.generator.anchor.GeneratorAnchorModule for more details about anchor usage 102 | */ 103 | @SuppressWarnings("unchecked") 104 | public static Class generate(final Class type, 105 | final Class scope, 106 | final Class anchor) { 107 | Preconditions.checkNotNull(type, "Original type required"); 108 | Preconditions.checkArgument(type.isInterface() || Modifier.isAbstract(type.getModifiers()), 109 | "Type must be interface or abstract class, but provided type is not: %s", type.getName()); 110 | 111 | final String targetClassName = type.getName() + DYNAMIC_CLASS_POSTFIX; 112 | final ClassLoader classLoader = type.getClassLoader(); 113 | 114 | /* 115 | * Synchronization is required to avoid double generation and consequent problems. 116 | * Very unlikely that this method would be called too often and synchronization become bottleneck. 117 | * Using original class as monitor to allow concurrent generation for different classes. 118 | */ 119 | synchronized (type) { 120 | Class targetClass; 121 | try { 122 | // will work if class was already generated 123 | targetClass = classLoader.loadClass(targetClassName); 124 | } catch (ClassNotFoundException ex) { 125 | targetClass = generateClass(type, targetClassName, classLoader, scope, anchor); 126 | } 127 | return (Class) targetClass; 128 | } 129 | } 130 | 131 | private static Class generateClass(final Class type, final String targetClassName, 132 | final ClassLoader classLoader, 133 | final Class scope, 134 | final Class anchor) { 135 | try { 136 | // have to use custom pool because original type classloader could be thrown away 137 | // and all cached CtClass objects would be stale 138 | final ClassPool classPool = new ClassPool(); 139 | classPool.appendClassPath(new LoaderClassPath(classLoader)); 140 | 141 | final CtClass impl = generateCtClass(classPool, targetClassName, type, scope, anchor); 142 | // incompatible javassist apis for java 8 and >=9 143 | return Utils.isJava8() ? impl.toClass(classLoader, type.getProtectionDomain()) : impl.toClass(type); 144 | } catch (Exception ex) { 145 | throw new DynamicClassException("Failed to generate class for " + type.getName(), ex); 146 | } 147 | } 148 | 149 | private static CtClass generateCtClass(final ClassPool classPool, final String targetClassName, final Class type, 150 | final Class scope, 151 | final Class anchor) 152 | throws Exception { 153 | 154 | final CtClass ctType = classPool.get(type.getName()); 155 | final CtClass ctAnchor = anchor == null ? null : classPool.getCtClass(anchor.getName()); 156 | final CtClass impl; 157 | if (type.isInterface()) { 158 | impl = classPool.makeClass(targetClassName); 159 | impl.addInterface(ctType); 160 | } else { 161 | impl = classPool.makeClass(targetClassName, ctType); 162 | final Constructor diConstructor = findDIConstructor(type); 163 | if (diConstructor != null) { 164 | copyConstructor(impl, ctType, diConstructor, ctAnchor); 165 | } 166 | } 167 | if (anchor != null && impl.getConstructors().length == 0) { 168 | // create new constructor with anchor dependency 169 | createAnchorConstructor(impl, ctAnchor); 170 | } 171 | final ConstPool constPool = impl.getClassFile().getConstPool(); 172 | final AnnotationsAttribute annotations = copyAnnotations(classPool, constPool, type); 173 | impl.getClassFile().addAttribute(annotations); 174 | applyScopeAnnotation(classPool, annotations, type, scope); 175 | return impl; 176 | } 177 | 178 | private static void copyConstructor(final CtClass impl, final CtClass ctType, final Constructor ctor, 179 | final CtClass anchor) throws Exception { 180 | final ClassPool classPool = impl.getClassPool(); 181 | final CtClass[] parameters = JavassistUtils.convertTypes(classPool, ctor.getParameterTypes()); 182 | final CtConstructor ctConstructor = CtNewConstructor.make( 183 | parameters, 184 | JavassistUtils.convertTypes(classPool, ctor.getExceptionTypes()), 185 | CtNewConstructor.PASS_PARAMS, null, null, impl); 186 | if (anchor != null) { 187 | ctConstructor.addParameter(anchor); 188 | } 189 | final ConstPool constPool = impl.getClassFile().getConstPool(); 190 | final MethodInfo methodInfo = ctConstructor.getMethodInfo(); 191 | methodInfo.addAttribute(copyAnnotations(classPool, constPool, ctor)); 192 | methodInfo.addAttribute(copyConstructorParametersAnnotations(classPool, constPool, ctor, anchor != null)); 193 | final SignatureAttribute info = copyConstructorGenericsSignature(constPool, parameters, ctType, anchor); 194 | if (info != null) { 195 | methodInfo.addAttribute(info); 196 | } 197 | impl.addConstructor(ctConstructor); 198 | } 199 | 200 | private static void createAnchorConstructor(final CtClass impl, final CtClass anchor) 201 | throws Exception { 202 | final ClassPool classPool = impl.getClassPool(); 203 | final CtConstructor ctConstructor = CtNewConstructor.make( 204 | new CtClass[]{anchor}, 205 | null, 206 | CtNewConstructor.PASS_NONE, null, null, impl); 207 | final ConstPool constPool = impl.getClassFile().getConstPool(); 208 | final MethodInfo methodInfo = ctConstructor.getMethodInfo(); 209 | // add injection annotation 210 | final AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); 211 | final Annotation annotation = new Annotation(constPool, classPool.get(Inject.class.getName())); 212 | attr.addAnnotation(annotation); 213 | methodInfo.addAttribute(attr); 214 | impl.addConstructor(ctConstructor); 215 | } 216 | 217 | private static Constructor findDIConstructor(final Class type) { 218 | Constructor target = null; 219 | for (Constructor ctor : type.getConstructors()) { 220 | if (ctor.isAnnotationPresent(Inject.class)) { 221 | target = ctor; 222 | break; 223 | } 224 | // manual search to avoid direct dependency on javax and jakarta namespace 225 | for (java.lang.annotation.Annotation ann : ctor.getAnnotations()) { 226 | final String name = ann.annotationType().getName(); 227 | if (JAVAX_INJECT.equals(name) || JAKARTA_INJECT.equals(name)) { 228 | target = ctor; 229 | break; 230 | } 231 | } 232 | } 233 | return target; 234 | } 235 | 236 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 237 | private static ParameterAnnotationsAttribute copyConstructorParametersAnnotations( 238 | final ClassPool classPool, final ConstPool constPool, final Constructor ctor, 239 | final boolean anchorAdded) throws Exception { 240 | final int count = ctor.getParameterTypes().length; 241 | final Annotation[][] paramAnnotations = new Annotation[count + (anchorAdded ? 1 : 0)][]; 242 | for (int i = 0; i < count; i++) { 243 | final java.lang.annotation.Annotation[] anns = ctor.getParameterAnnotations()[i]; 244 | paramAnnotations[i] = new Annotation[anns.length]; 245 | for (int j = 0; j < anns.length; j++) { 246 | paramAnnotations[i][j] = JavassistUtils.copyAnnotation(classPool, constPool, anns[j]); 247 | } 248 | } 249 | if (anchorAdded) { 250 | paramAnnotations[count] = new Annotation[0]; 251 | } 252 | final ParameterAnnotationsAttribute paramAnns = new ParameterAnnotationsAttribute( 253 | constPool, ParameterAnnotationsAttribute.visibleTag); 254 | paramAnns.setAnnotations(paramAnnotations); 255 | return paramAnns; 256 | } 257 | 258 | @SuppressWarnings("PMD.UnusedPrivateMethod") 259 | private static void applyScopeAnnotation(final ClassPool classPool, final AnnotationsAttribute annotations, 260 | final AnnotatedElement source, 261 | final Class scope) 262 | throws Exception { 263 | if (scope != null) { 264 | Preconditions.checkState(Annotations.isScopeAnnotation(scope), 265 | "Provided annotation %s is not scope annotation", scope.getSimpleName()); 266 | for (java.lang.annotation.Annotation ann : source.getAnnotations()) { 267 | Preconditions.checkArgument(!(ann instanceof ScopeAnnotation), 268 | "Duplicate scope definition: scope is specified as %s and also defined " 269 | + "in @ScopeAnnotation.", scope.getSimpleName()); 270 | } 271 | annotations.addAnnotation(new Annotation(annotations.getConstPool(), classPool.get(scope.getName()))); 272 | } 273 | } 274 | 275 | private static AnnotationsAttribute copyAnnotations(final ClassPool classPool, final ConstPool constPool, 276 | final AnnotatedElement source) throws Exception { 277 | final AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); 278 | if (source.getAnnotations().length > 0) { 279 | 280 | for (java.lang.annotation.Annotation ann : source.getAnnotations()) { 281 | final Annotation annotation = processAnnotation(classPool, constPool, ann); 282 | if (annotation != null) { 283 | attr.addAnnotation(annotation); 284 | } 285 | } 286 | } 287 | return attr; 288 | } 289 | 290 | private static Annotation processAnnotation(final ClassPool classPool, final ConstPool constPool, 291 | final java.lang.annotation.Annotation ann) throws Exception { 292 | Annotation res = null; 293 | // if we copy these annotation guice will go to infinite loop 294 | if (!(ann instanceof ProvidedBy || ann instanceof ImplementedBy)) { 295 | Preconditions.checkState(!Annotations.isScopeAnnotation(ann.annotationType()), 296 | "Don't use scope annotations directly - use @ScopeAnnotation(TargetScope) wrapper, " 297 | + "because guice doesn't allow scope annotations on abstract types"); 298 | if (ann instanceof ScopeAnnotation) { 299 | res = new Annotation(constPool, 300 | classPool.get(((ScopeAnnotation) ann).value().getName())); 301 | } else { 302 | res = JavassistUtils.copyAnnotation(classPool, constPool, ann); 303 | } 304 | } 305 | return res; 306 | } 307 | 308 | private static SignatureAttribute copyConstructorGenericsSignature( 309 | final ConstPool constPool, final CtClass[] params, final CtClass source, final CtClass anchor) 310 | throws Exception { 311 | final CtConstructor ctConstructor = source.getConstructor(Descriptor.ofConstructor(params)); 312 | String signature = null; 313 | for (Object attr : ctConstructor.getMethodInfo().getAttributes()) { 314 | if (attr instanceof SignatureAttribute) { 315 | signature = ((SignatureAttribute) attr).getSignature(); 316 | break; 317 | } 318 | } 319 | if (signature != null && anchor != null) { 320 | // add anchor to generics signature 321 | final String type = "L" + (anchor.getName().replaceAll("\\.", "/")) + ";"; 322 | final int idx = signature.lastIndexOf(')'); 323 | signature = signature.substring(0, idx) + type + signature.substring(idx); 324 | } 325 | return signature == null ? null : new SignatureAttribute(constPool, signature); 326 | } 327 | } 328 | --------------------------------------------------------------------------------