Java 在CDI容器中手动注册类

Java 在CDI容器中手动注册类,java,cdi,Java,Cdi,我有一组由反射实例化的类,因此这些类不由CDI容器管理,也不由上下文进行注入。我的问题是,有没有办法在CDI上下文中注册这些类,以便这些类由上下文管理 下面是我创建类的方式: String clazz = "org.myorg.thisIsMyClass"; MyClass myClass = Class.forName(clazz).newInstance(); // myClass instance not managed by CDI 如何让CDI容器管理myClass的实例?您可以让C

我有一组由反射实例化的类,因此这些类不由CDI容器管理,也不由上下文进行注入。我的问题是,有没有办法在CDI上下文中注册这些类,以便这些类由上下文管理

下面是我创建类的方式:

String clazz = "org.myorg.thisIsMyClass";
MyClass myClass = Class.forName(clazz).newInstance(); // myClass instance not managed by CDI

如何让CDI容器管理
myClass
的实例?

您可以让CDI知道您的实例,而不是自己实例化bean(正如我在上一篇文章中指出的那样),而是让CDI实例化bean。下面是一个示例代码:

InitialContext initialContext = new InitialContext();
BeanManager bm = (BeanManager) initialContext.lookup("java:comp/BeanManager");

//List all CDI Managed Beans and their EL-accessible name
Set<Bean<?>> beans = bm.getBeans(AbstractBean.class, new AnnotationLiteral<Any>() {});
List<Object> beanInstances = new ArrayList<Object>();

for (Bean bean : beans) {
    CreationalContext cc = bm.createCreationalContext(bean);
    //Instantiates bean if not already in-service (undesirable)
    Object beanInstance = bm.getReference(bean, bean.getBeanClass(), cc);
    beanInstances.add(beanInstance);
}

return beanInstances;
InitialContext InitialContext=new InitialContext();
BeanManager bm=(BeanManager)initialContext.lookup(“java:comp/BeanManager”);
//列出所有CDI托管bean及其EL可访问名称

Set让CDI管理类的最简单方法是使用制作人

public class MyProducers {

  @Produces
  @RequestScoped //Could be any scope here
  @FromReflection //a qualifier to distinguish this Bean of type Object from others. Would be better to have a more specific bean type if all your class come from the same ancestor.
  public Object produceMyClass()
  {
    String clazz = "org.myorg.thisIsMyClass";
    Object myObject = Class.forName(clazz).newInstance();
    return myObject;
  }
}
在代码的其他地方,您可以像这样使用此生成器:

@Inject
@FromReflection
Object myBean;
**编辑:添加
InjectionPoint
用法**

现在,您可以通过在其参数列表中注入它的
InjectionPoint
来增强生产者。然后,您可以使用注入点(即限定符)的元数据来动态查找您的类

首先,您必须添加一个字段以在
@FromReflection
限定符中存储类名:

@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface FromReflection {

    @Nonbinding String value(); // classname will be store here 
}
然后在制作人中使用此信息:

public class MyProducers {

  private String extractClassName(InjectionPoint ip) {
    for (Annotation annotation : ip.getQualifiers()) {
        if (annotation.annotationType().equals(FromReflection.class))
            return ((FromReflection) annotation).value();
    }
    throw new IllegalStateException("No @FromReflection on InjectionPoint");
  }

  @Produces
  @FromReflection
  public Object produceMyClass(InjectionPoint ip)
  {
    String clazzNanme = extractClassName(ip);
    Object myObject = Class.forName(clazz).newInstance();
    return myObject;
  }

}
注意,生成的bean必须在
@Dependent
范围内,当在producer参数中注入
InjectionPoint
时,它是一个约束。 现在可以像这样注入bean:

@Inject
@FromReflection("org.myorg.thisIsMyClass")
Object myBean;
现在,如果您想在运行时决定要构建哪个类,那么必须使用CDI编程查找功能,该功能允许您创建合成限定符。 首先为您的限定符创建一个AnnotationLiteral,以便能够实例化一个新的限定符

public class FromReflectionLiteral extends AnnotationLiteral<FromReflection> implements FromReflection {

    private String value;

    public FromReflectionLiteral(String value) {
        this.value = value;
    }

    @Override
    public String value() {
        return value;
    }
}
公共类FromReflectionLiteral扩展了AnnotationLiteral实现FromReflection{
私有字符串值;
public FromReflectionLiteral(字符串值){
这个值=值;
}
@凌驾
公共字符串值(){
返回值;
}
}
然后,您将使用
实例
bean来请求您的最终bean

public class ConsumingBean {

    @Inject
    @Any
    Instance<Object> myBeanInstance;

    public Object getBeanFor(String className) {
     return myBeanInstance.select(new FromReflectionLiteral(className)).get();
    }
    ...
}
公共类消费豆{
@注入
@任何
实例myBeanInstance;
公共对象GetBean(字符串类名称){
返回myBeanInstance.select(newfromReflectionLiteral(className)).get();
}
...
}

下一步是使用一个可移植的扩展…

在@AdrianMitev的注释之后,我最终编写了这个类,该类根据其类名(elName)或类类型返回托管CDI Bean的实例:

public class GetInstance {
    public static Object of(String elName) {
       BeanManager bm = getBeanManager();
       Bean<?> bean = bm.resolve(bm.getBeans(elName));
       return bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
    }

    @SuppressWarnings("unchecked")
    public static <T> T of(Class<T> clazz) {
        BeanManager bm = getBeanManager();
        Bean<?> bean = bm.resolve(bm.getBeans(clazz));
        return (T) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
    }

    private static BeanManager getBeanManager() {
        try {
            return (BeanManager) new InitialContext().lookup("java:comp/BeanManager");
        } catch (NamingException e) {
            e.printStackTrace();
        }
        return null;
    }
}
您可以使用以下方法获取托管CDI实例的许可证:

FooClass fC = GetInstance.of(FooClass.class);
或使用其
elName

FooClass fC = (FooClass) GetInstance.of("fooClass");
或者,您可以选择要使用的名称:

@Named(value="CustomFooClassName")
public class FooClass {
...
}
并使用:

FooClass fC = (FooClass) GetInstance.of("CustomFooClassName");

如果您的类被容器注册为bean,那么您可以使用编程查找来轻松获取它们

@Inject
@Any
Instance<Object> myBeans;

public Object getMyBeanFromClassName(String className) throws Exception{    
    Class clazz = Class.forName(className);
    return myBeans.select(clazz).get();  
}
@Inject
@任何
实例myBeans;
公共对象getMyBeanFromClassName(字符串className)引发异常{
Class clazz=Class.forName(className);
返回myBeans.select(clazz.get();
}

Et voilá。

您的方法很有趣,尽管您可能知道,类的名称并不是一个常量,我使用了一个常量作为说明;使用反射是因为要创建的类是未知的,可以是任何确定类型的类,因此它是可变的。那么,您的方法如何将参数接受到
@producer
方法中呢?通过创建另一个
@producer
?您可以通过使用
InjectionPoint
(我修改了我的答案以向您展示如何操作),缺点是您生成的bean必须在
@依赖范围内。您必须使用一个包含bean来给它一个作用域。也可能有基于可移植扩展的解决方案,但我真的需要更多地了解您的用例。这是您的好代码,尽管我最终使用了另一种方法,但您的方法也是有效的,事实上,要找到有关
注释文字
生产者的动态参数的示例并不容易,就像你的那个。我在这篇文章的答案中发布了我的方法,你可以看到我最终实现了什么。我的代码回答了最初的问题。你的没有。只有当您的类被CDI容器发现并注册为bean类时,它才起作用,因为CDI不允许在运行时创建新的bean。如果您的bean能够有效地管理这段代码,那么我可以告诉您,看到这段代码会让我的CDI规范变得冷淡。它可以工作,但完全超出了CDI的精神……严格地说,是的,这是针对CDI已经发现的类。整个想法是在不使用
@Inject
的情况下获取由CDI管理的类的实例。假设您有50个类,并且在运行时您知道要使用什么类,那么将所有50个类注入到一个类中并使用一个类是没有意义的。由CDI发现一个类的事实并不保证您将获得一个托管实例,而且在不使用
@Inject
的情况下获得由CDI管理的实例绝非易事。因此,不要关注类是否被发现,问题是如何管理实例。很抱歉,这个解决方案是错误做法的汇编:当您可以注入BeanManager时,从JNDI检索BeanManager。当您有更高级别的工具(如编程查找)时,直接使用bean管理器;当您可以从bean的类型请求bean时,使用慢速EL解析;当CDI基于强类型时,对名称进行解析,并且仅为UI提供名称解析。我发布了一个新的答案,用更少的行和CDI良好的实践来做同样的事情。好吧,你可以把我的答案吐得到处都是,但是你忘记了最重要的部分。不,如果没有注意到创建的实例,则不能插入
BeanManager
@Inject
@Any
Instance<Object> myBeans;

public Object getMyBeanFromClassName(String className) throws Exception{    
    Class clazz = Class.forName(className);
    return myBeans.select(clazz).get();  
}