【问题标题】:What is the best way to handle different injectable implementations?处理不同可注入实现的最佳方法是什么?
【发布时间】:2019-08-06 08:51:44
【问题描述】:

所以我有一个抽象的 Java 类和一些实现(在我的例子中是 5 个)。

看起来像这样:

public abstract class AbstractAbst {
    String someCommonString;

    @Inject
    Logger logger;

    public AbstractAbst () {
    }

    public AbstractAbst (String someCommonString) {
        this.someCommonString = someCommonString;
    }
}

public class Abst1 extends AbstractAbst {
    public Abst1() {
        super();
    }

    public Abst1 (String someCommonString) {
        super(someCommonString);
    }
}

public class Abst2 extends AbstractAbst {
    public Abst2 () {
        super();
    }

    public Abst2 (String someCommonString) {
        super(someCommonString);
    }
}

// ... And 3 more

现在在我想使用这些类的班级中,我这样做:

public class AbstUser {
    @Inject
    Abst1 abst1;
    @Inject
    Abst2 abst2;
    // ... And 3 more

    public AbstUser () {

    }

    public doSomething {
        abst1.someCommonString = "test";
        abst2.someCommonString = "test";
        // ... And 3 more

        switch(someDecision) {
            case 1:
                abst1.doSomethingInAbst1();
                break;
            case 2:
                abst1.doSomethingInAbst1();
                break;
            // ... And 3 more
        }
    }
}

但这不是最好的方法。不幸的是,我对 JavaEE 还很陌生,所以我想不出更好的方法。

我的问题:

  • 有没有办法注入构造函数参数,这样我就可以 省略 abst1.someCommonString = "test" 部分,因为它实际上 更多的体积,因为它不仅仅是一个参数。
  • 我能以某种方式 注入抽象类并在运行时确定哪个 我想使用的实现?

【问题讨论】:

  • 据我所知,你注入的类型需要在构造 bean 时解析。
  • @RobbyCornelissen 那么你会如何解决这样的问题呢?
  • 没有足够的上下文来进行调用。使用提供的信息:不要注入任何东西,只需在运行时构造所需的类型。如果您还有其他方法可以确定需要注入的内容,可以查看Instance
  • 我需要注入它,因为抽象类也注入了一些东西。或者你可以实例化一个注入某些东西的类的对象,而不注入那个类?
  • 你可以使用BeanManager来做类似的时髦的事情。

标签: jakarta-ee cdi


【解决方案1】:

通过将@Inject 放在重载的构造函数上,您可以将其定义为注入点并通过它解析您的参数:

public abstract class Abst {
String someCommonString;

@Inject
Logger logger;

public Abst () {
}
@Inject
public Abst (@Named("myString") String someCommonString) {
    this.someCommonString = someCommonString;
}
}


public class AbstUser {
@Produces
@Named("myString")
public String getMyString() {
return "test";
}

在 getMyString 函数中,您返回应该放在使用 @Named("myString") 的位置的字符串

【讨论】:

  • 但要小心:一个bean只能有一个可注入的构造函数。
  • 如何从使用这个构造函数注入Abst1/2/... 的类中注入someCommonString
【解决方案2】:

CDI 生产者可以帮助解决这个用例:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Qualifier
@interface Value {
    @Nonbinding String value() default "";
}

public class ValueProducer {
    @Produces
    @Value
    @Default
    public String produceValue(InjectionPoint ip) {
        Value val = ip.getAnnotated().getAnnotation(Value.class);
        // get the value somehow, for this example we return value provided with annotation
        return val.value();
    }
}

public class Abst1 extends AbstractAbst {
    @Inject
    public Abst1 (@Value("value1") String someCommonString) {
        super(someCommonString);
    }
}

public class Abst2 extends AbstractAbst {
    @Inject
    public Abst2 (@Value("value2") String someCommonString) {
        super(someCommonString);
    }
}

【讨论】:

    【解决方案3】:

    我假设您正在尝试构建:

    public class AbstUser {
        @Inject
        AbstractAbst abst;
    
        public doSomething() {
            abst.doSomethingInAbst();
        }
    
    }
    

    AbstractAbst 的声明与您提供的一样,尽管要调用一个抽象方法:

    public abstract class AbstractAbst {
    
        @Inject
        @Named("myString")
        String someCommonString;
    
        @Inject
        Logger logger;
    
        public abstract void doSomethingInAbst();
    
        ...
    
    }
    

    并且您想确定在运行时注入的AbstractAbst 的实际子类型。

    做到这一点的方法是将javax.inject.Qualifier生产者结合使用。

    限定符用于消除注入点类型为超类型时的歧义。

    为了论证,假设您要计算要注入的AbstractAbst 的具体类型。这将是一个“计算的 AbstractAbst`。因此我们为此目的创建了一个限定符:

    @Retention(RUNTIME)
    @Target({TYPE, METHOD, FIELD, PARAMETER})
    public @interface Computed {
    }
    

    限定词名称通常是形容词。我希望您能想出一个更好的领域特定形容词,而不是“计算”。

    现在我们需要生成一个Computed AbstractAbst 对象。生产者方法通常写在工厂类中,但它们可以添加到任何类型的 bean 中。我们将javax.enterprise.inject.Instance 实例变量(或成员、字段——你喜欢怎么称呼它们)注入AbstComputer 对象,以便我们可以在需要时访问Abst1Abst2 对象的新完全实现实例。换句话说,它们自己的所有实例变量都已被完全注入。参见javax.inject.Provider.get() - javax.inject.Providerjavax.enterprise.inject.Instance 的超接口。

    ...
    import javax.enterprise.inject.Produces;
    
    
    public class AbstComputer {
    
        @Inject
        Instance<Abst1> abst1Instance;
    
        @Inject
        Instance<Abst2 abst2Instance;
    
        // and three more
    
        @Produces
        @Computed
        AbstractAbst computeAbst() {
            // arbitrary logic to determine which type of AbstractAbst to create.
            if (useAbst1())
                return abst1Instance.get();
            else if (useAbst2())
                return abst2Instance.get();
            // and three more
            ...
        }
    
    
       /**
        * This is here for completeness, but it could be anywhere.
        * It is copied from @theMahaloRecords solution.
        */
       @Produces
       @Named("myString") 
       String lookupMyString() {
          return "test";
       }
    
    }
    

    @theMahaloRecords 值得称赞,因为它提供了一种产生“someCommonString”值的解决方案。

    请注意,许多开发人员宁愿声明一个自定义的 Qualifier 而不是为此使用 @Named,因为它是完全类型安全的,并且不存在拼写错误的危险,例如 @Named("mystring")

    最后,您需要在注入点包含限定符:

    public class AbstUser {
    
        @Inject
        @Computed
        AbstractAbst abst;
    
        public doSomething {
            abst.doSomethingInAbst();
        }
    
    }
    

    【讨论】:

    • 感谢您的时间和精力。看起来不错,只有一个问题:在computeAbst() 中,如何将决策所基于的参数传递给方法?所以基本上,我怎样才能将我的问题中的someDecision 变量传递给这个?
    • 如何提供这个变量?在属性文件中?作为-DsomeDecision="foo" 命令行参数?你看过MicroProfile Config吗?
    • 不,它只是一个之前计算的本地字符串。
    • 您需要按照@theMahaloRecords 描述的方式使该变量可用于注入
    猜你喜欢
    • 2010-09-07
    • 2015-05-31
    • 1970-01-01
    • 2010-09-06
    • 1970-01-01
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多