@InterceptorBinding / CDI/ EJB 3.2 - 注入问题

@InterceptorBinding / CDI/ EJB 3.2 - problems with injection

我的开发环境是:WildFly 8.1、CDI、EJB 3.2、JDK1.7。应用程序打包为一个 ear 存档(一个 ejb + 一个 war),因为将来它可能会有其他网络模块。

我正在努力处理在我的 EJB 无状态 bean 中使用的自定义 @InterceptorBinding 类型。

@Inherited
@InterceptorBinding
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@Documented
public @interface DetectIntegrityConstraintsViolation {
}

@javax.annotation.ManagedBean  // make it a CDI bean. @Interceptor itself should be enough, but WildFly 8.1 seems to have a bug, since it doesn't work without this line.
@Interceptor
@DetectIntegrityConstraintsViolation
public class ReferentialIntegrityConstraintViolationInterceptor {

    @PersistenceContext
    private EntityManager em;

    @Resource
    private SessionContext sessionContext;

    // ....
 }

beans.xml:

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       version="1.1" bean-discovery-mode="annotated">

  <interceptors>
    <class>com.xxx.ejb.ReferentialIntegrityConstraintViolationInterceptor</class>
  </interceptors>
</beans>

当我通过 REST 服务调用我的 EJB 方法时,我得到 Error injecting resource into CDI managed bean:

javax.naming.NameNotFoundException: Caused by java.lang.IllegalStateException: JBAS011048: Failed to construct component instance  Caused by: java.lang.IllegalArgumentException: JBAS016081: Error injecting resource into CDI managed bean.
Can't find a resource named java:comp/env/com.xxx.ejb.ReferentialIntegrityConstraintViolationInterceptor/sessionContext defined on private javax.ejb.SessionContext com.xxx.ejb.ReferentialIntegrityConstraintViolationInterceptor.sessionContext   at org.jboss.as.weld.services.bootstrap.WeldResourceInjectionServices.resolveResource(WeldResourceInjectionServices.java:188) [wildfly-weld-8.1.0.Final.jar:8.1.0.Final]

所以在黑暗中行走,我转向了 ResourceLookup 方法:

@ManagedBean
@Interceptor
@DetectIntegrityConstraintsViolation
public class ReferentialIntegrityConstraintViolationInterceptor {

    @PersistenceContext
    private EntityManager em;

    private SessionContext sessionContext;

    @PostConstruct
    public void init(InvocationContext ctx) {
        try {
            InitialContext ic = new InitialContext();
            this.sessionContext = (SessionContext)ic.lookup("java:comp/EJBContext");
        } catch (NamingException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

    // .....
}

然后注入开始工作,但是我得到了一个新的错误:

Caused by: org.jboss.weld.exceptions.DefinitionException: WELD-000619: An interceptor for lifecycle callbacks Interceptor [class com.xxx.ejb.ReferentialIntegrityConstraintViolationInterceptor intercepts @DetectIntegrityConstraintsViolation] declares and interceptor binding interface com.xxx.ejb.DetectIntegrityConstraintsViolation with METHOD as its @Target.

因此,当从 DetectIntegrityConstraintsViolation 中删除一个 METHOD 目标时:

@Inherited
@InterceptorBinding
@Target({ TYPE /*, METHOD*/ }) // CRUCIAL
@Retention(RUNTIME)
@Documented
public @interface DetectIntegrityConstraintsViolation {
}

然后开始工作。但是为什么??
为什么我不能在方法上放置注释?有人知道吗?

顺便说一句:更奇怪的是,当我没有使用 @InterceptorBinding,而是普通的旧版本时:

@Override
// @DetectIntegrityConstraintsViolation
@Interceptors(ReferentialIntegrityConstraintViolationInterceptor.class)
public User updateUser(final User user) {
    // ...
}

拦截器即使在方法级别也能完美运行。

我发现 EJB 和 Weld 非常适合使用 awkward...

CDI spec:

中描述了您的一条错误消息

An interceptor for lifecycle callbacks may only declare interceptor binding types that are defined as @Target(TYPE). If an interceptor for lifecycle callbacks declares an interceptor binding type that is defined @Target({TYPE, METHOD}), the container automatically detects the problem and treats it as a definition error.

您已经使用 @PostConstruct init(InvocationContext ctx) 创建了一个生命周期回调。此回调旨在 运行 构建您正在拦截的 bean,因此将其应用于方法没有意义。

至于为什么旧的 @Interceptor 有效,documentation 中也有描述:

An around-invoke interceptor may be defined to apply only to a specific method of the target class. Likewise, an around-timeout interceptor may be defined to apply only to a specific timeout method of the target class. However, if an interceptor class that defines lifecycle callback interceptor methods is defined to apply to a target class at the method level, the lifecycle callback interceptor methods are not invoked.

至于这个:

I find EJB and Weld so awkward to use...

如果你放慢速度并边学边学,你会过得更轻松。您似乎在尝试随机的事情并且对结果感到困惑,如果您不熟悉 CDI 和 EJB,这是可以预料的。

我还担心您正在使用 @ManagedBean 注释。第一,它实际上是 deprecated,第二,它用于 JSF,您没有说您正在使用它。

多亏了@DavidS,我才得以成功。为他点赞。

他指给我看,@Interceptor里面的@PostConstruct和我想的不一样

下面是带注释的正确代码:

@Inherited
@InterceptorBinding
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@Documented
public @interface DetectIntegrityConstraintsViolation {
}


import javax.annotation.ManagedBean;
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.persistence.PersistenceContext;

@ManagedBean // make it a CDI bean. @Interceptor itself should be enough, but WildFly 8.1 seems to have a bug, since it doesn't work without this line.
@Interceptor
@DetectIntegrityConstraintsViolation
public class ReferentialIntegrityConstraintViolationInterceptor {

    @PersistenceContext
    private EntityManager em;

    @Resource(name = "java:comp/EJBContext") // injecting Java EE resource into CDI bean
    private SessionContext sessionContext;

    @AroundInvoke
    public Object processInvocation(InvocationContext ctx) throws Exception {
        // ...
    }
 }

现在我可以在我的 EJB bean 中使用了:

@DetectIntegrityConstraintsViolation
public User updateUser(final User user) {
    // ...
}

而不是:

@Interceptors(ReferentialIntegrityConstraintViolationInterceptor.class)
public User updateUser(final User user) {
    // ...
}

以后来的人说明:

@InterceptorBinding 机制来自于 CDI 世界,因此拦截器必须成为 CDI bean 本身。它会带来某些后果:

  • 必须在 beans.xml
  • 中明确指定拦截器
  • 拦截器(如果bean-discovery-mode="annotated")必须是一个CDI bean;所以用 @javax.interceptor.Interceptor 注释它。不幸的是 WildFly 8.1 似乎有一些错误,因为它拒绝在没有 @javax.annotation.ManagedBean.
  • 的情况下工作
  • 一些Java EE 资源注入(如 EJB SessionContext)在拦截器中是通过 JNDI 完成的