CDI Extension用于自动将对远程EJB的调用从一个EAR解析到另一个EAR

时间:2014-11-28 11:54:41

标签: java ejb cdi websphere-8

我在尝试从另一个EAR调用部署在EAR中的远程服务时遇到问题。 我没有为我的EJB指定任何名称,无论它们是@Local还是@Remote,所以只需使用注释并通过@EJB注入它。

这就是我所拥有的:

  • EAR A /

    • lib /任何lib jar(包括远程服务B的API jar)
    • ejb模块与服务A呼叫远程服务B
  • EAR B /

    • lib /任何API lib jar
    • 带服务B的ejb模块

其他信息:服务B实现@Local和@Remote接口,服务A通过以下方式注入带有远程接口的服务B:

@EJB private MyRemoteInterface remoteService;

这个结构与jboss服务器完全兼容,但是对于websphere(8.5.5.1),我必须将名称绑定到我的远程EJB上。如果不在两个EAR上添加绑定(我通过管理控制台直接执行它而不必编辑ejb-jar.xml),那么我的远程bean在运行时不会被解析。 当然,我必须让它与WAS一起使用,否则我不会发布:)

我的问题:用WebSphere命名远程EJB是否正常,或者它是回归(来自任何以前的版本)?我期望在远程bean上注入@EJB可以自动解决类型,但也许我错了?

解决方案: 因为必须进行查找才能使解析工作,所以我决定将查找配置部分添加到客户端ejb-jar.xml文件中。这是通过maven插件执行自动完成的,查找名称基于远程接口全名(包括包),因为如果在EJB实现中没有指定任何内容,这是WebSphere使用的默认绑定。

我选择此解决方案有两个原因:

  • 我不想在我的代码中进行查找(没有兴趣的重复代码)
  • 我需要让其他开发者自动且透明地

感谢bkail的回答。

2 个答案:

答案 0 :(得分:1)

这正如WebSphere Application Server所期望的那样工作,并且它不是回归。 javadoc仅在类型位于同一应用程序中时才需要@EJB的自动绑定:

  

如果没有提供明确的链接信息且只有一个   会话bean在同一个应用程序中公开匹配   客户端视图类型,默认情况下,EJB依赖关系解析为该类型   会话bean。

答案 1 :(得分:1)

最后,出于业务延迟的原因,我已经写了一份CDI扩展来完成这项工作。 扩展程序使用远程合同扫描所有注入点并将其代理。代理是按需创建的@ApplicationScoped托管bean,其作业仅包括:

  • 查找与扫描的远程合同相关的目标bean
  • 委托执行被调用的远程方法

这个解决方案还为我提供了通过ENV变量配置来处理不同机器上的查找操作的可能性,这样容器(即Docker)的部署就可以轻松实现(这是我们未来的目标之一)< / p>

编辑:CDI扩展代码

<强> RemoteEjbExtension.java

public class RemoteEjbExtension implements Extension {

/**
 * This method is fired by the container for every Java EE component class
 * supporting injection that may be instantiated by the container at runtime,
 * including every managed bean declared using javax.annotation.ManagedBean,
 * EJB session or message-driven-bean, enabled bean, enabled interceptor or
 * enabled decorator.
 *
 * @param pit the event that has been fired
 */
<T> void processInjectionTarget(@Observes final ProcessInjectionTarget<T> pit) {
    for (AnnotatedField<? super T> field : pit.getAnnotatedType().getFields()) {
        if (field.getJavaMember().getType().isAnnotationPresent(Remote.class)) {
            RemoteProxyFactory.putIfAbsent(field.getJavaMember().getType());
        }
    }
}

/**
 * This method is fired by the container when it has fully completed the
 * bean discovery process, validated that there are no definition errors
 * relating to the discovered beans, and registered Bean and ObserverMethod
 * objects for the discovered beans, but before detecting deployment problems.
 *
 * @param abd AfterBeanDiscovery fired events
 * @param bm Allows a portable extension to interact directly with the container.
 *          Provides operations for obtaining contextual references for beans,
 *          along with many other operations of use to portable extensions.
 */
@SuppressWarnings("unchecked")
void afterBeanDiscovery(@Observes final AfterBeanDiscovery abd, final BeanManager bm) {

    // Roll over discovered remote interfaces
    for (final Entry<String, Class<?>> remoteClassEntry : RemoteProxyFactory.getProxyClassEntries()) {

        // Proxy that points to the remote EJB
        final Object remoteProxy;
        final Class<?> remoteClass = remoteClassEntry.getValue();

        try {
            // Build a proxy that fetches the remote EJB using JNDI
            // and delegate the call.
            remoteProxy = RemoteProxyFactory.Builder.createEJBRemoteProxy(remoteClass);
        } catch (Exception e) {
            throw new IllegalStateException("Proxy creation for " + remoteClass.getCanonicalName() + " failed.", e);
        }

        final InjectionTarget<Object> it;
        try {
            AnnotatedType<Object> at = ((AnnotatedType<Object>) bm.createAnnotatedType(remoteProxy.getClass()));
            it = bm.createInjectionTarget(at);
        } catch (Exception e) {
            throw new IllegalStateException("Injection target for " + remoteClass.getCanonicalName() + " is invalid.", e);
        }

        final Bean<?> beanRemoteProxy = RemoteProxyFactory.Builder.createBeanForProxy(remoteProxy, it, remoteClass, ApplicationScoped.class);
        abd.addBean(beanRemoteProxy);
    }

}
}

<强> RemoteProxyFactory.java

public final class RemoteProxyFactory {

/** The JNDI initial context */
private static InitialContext CTX;
static {
    try {
        RemoteProxyFactory.CTX = new InitialContext();
    } catch (NamingException e) {
        throw new IllegalStateException("Unable to get initial context.", e);
    }
}

private static final Map<String, Class<?>> REMOTE_EJB_CLASS_MAP = new ConcurrentHashMap<String, Class<?>>();

/**
 * Register given class into proxy map
 * @param remoteEJBContractClass the remote contract's class to register
 */
public static void putIfAbsent(final Class<?> remoteEJBContractClass) {
    // Works only for same class-loader. You would change this code
    // and transform the map to handle multiple class-loader for same contract.
    // In our current configuration there is no need as APIs / IMPL libraries share the same CL.
    if (!REMOTE_EJB_CLASS_MAP.containsKey(remoteEJBContractClass.getSimpleName())) {
        REMOTE_EJB_CLASS_MAP.put(remoteEJBContractClass.getSimpleName(), remoteEJBContractClass);
    }
}

public static Set<Entry<String, Class<?>>> getProxyClassEntries() {
    return REMOTE_EJB_CLASS_MAP.entrySet();
}

public static InitialContext getContext() {
    return RemoteProxyFactory.CTX;
}

public static final class Builder {

    private static final Logger LOGGER = Logger.getLogger(Builder.class.getName());

    /**
     * Create a new proxy that lookup the remote EJB
     * though JNDI.
     * @param remoteEJBClazz the remote class contract
     * @return a new remote EJB proxy
     */
    public static Object createEJBRemoteProxy(final Class<?> remoteEJBClazz) {
        return Proxy.newProxyInstance(remoteEJBClazz.getClassLoader(), new Class[] {
            remoteEJBClazz
        }, new InvocationHandler() {

            @Override
            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                Object ejbInstance = null;
                try {
                    // Pull the remote EJB from the JNDI
                    ejbInstance = RemoteProxyFactory.getContext().lookup(remoteEJBClazz.getName());
                } catch (Exception e) {
                    throw new IllegalStateException("Remote EJB not found : " + remoteEJBClazz.getSimpleName(), e);
                }
                // Delegates the call to the remote EJB
                return method.invoke(ejbInstance, args);
            }
        });
    }

    /**
     * Create a bean for given proxy / injection target / type / scope
     * @param proxy the proxy object
     * @param it the injection target
     * @param clazz the proxy type
     * @param targetScope the returned managed bean' scope
     * @return the managed bean handling given proxy
     */
    public static <T extends Object> Bean<T> createBeanForProxy(final T proxy, final InjectionTarget<T> it, final Class<?> clazz, final Class<? extends Annotation> targetScope) {
        return new Bean<T>() {

            @Override
            public T create(final CreationalContext<T> ctx) {
                return proxy;
            }

            @Override
            public void destroy(final T instance, final CreationalContext<T> ctx) {
                it.preDestroy(instance);
                it.dispose(instance);
                ctx.release();
            }

            @Override
            public Class<?> getBeanClass() {
                return clazz;
            }

            @Override
            public Set<InjectionPoint> getInjectionPoints() {
                return it.getInjectionPoints();
            }

            @Override
            public String getName() {
                return clazz.toString();
            }

            @Override
            public Set<Annotation> getQualifiers() {
                Set<Annotation> qualifiers = new HashSet<Annotation>();
                qualifiers.add(new AnnotationLiteral<Default>() {
                    /** Default serial-id. */
                    private static final long serialVersionUID = 1L;
                });
                qualifiers.add(new AnnotationLiteral<Any>() {
                    /** Default serial-id. */
                    private static final long serialVersionUID = 1L;
                });
                return qualifiers;
            }

            @Override
            public Class<? extends Annotation> getScope() {
                return targetScope;
            }

            @Override
            public Set<Class<? extends Annotation>> getStereotypes() {
                return Collections.emptySet();
            }

            @Override
            public Set<Type> getTypes() {
                Set<Type> types = new HashSet<Type>();
                types.add(clazz);
                return types;
            }

            @Override
            public boolean isAlternative() {
                return false;
            }

            @Override
            public boolean isNullable() {
                return false;
            }

        };
    }
}

}