You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无无参构造器时,如何将@ApplicationScoped Bean注入@Dependent作用域?

解决CDI中@ApplicationScoped Bean注入@Dependent的代理问题

这是我之前在Stack Overflow上探讨WELD对无参构造器要求问题的后续提问,当时我研究了WELD框架对Bean构造器的约束规则。

我现在遇到的核心问题是:想要把一个**@ApplicationScoped**(属于@Normal作用域)的Keycloak Bean注入到**@Dependent**作用域的Bean中,但这个Keycloak类是第三方库提供的,没有非私有的无参构造器,不符合WELD的代理要求,因此抛出错误:无法注入@Normal作用域Bean,因无无参构造器导致无法代理。

我的代码如下:

@Produces @ApplicationScoped @Named("keycloakAdmin") 
public Keycloak getKeycloakAdminClient(@Named("keycloakDeployment") final KeycloakDeployment deployment) { 
    String clientId = deployment.getResourceName(); 
    Map<String, Object> clientCredentials = deployment.getResourceCredentials(); 
    // 设置resteasy客户端连接池大小以保证线程安全
    ResteasyClient client = new ResteasyClientBuilder()
        .connectionPoolSize(CONNECTION_POOL_SIZE)
        .maxPooledPerRoute(CONNECTION_POOL_SIZE)
        .defaultProxy("localhost",8888) 
        .build(); 
    KeycloakBuilder builder = KeycloakBuilder.builder() 
        .clientId(clientId) 
        .clientSecret((String) clientCredentials.get(CredentialRepresentation.SECRET)) 
        .realm(deployment.getRealm()) 
        .serverUrl(deployment.getAuthServerBaseUrl()) 
        .grantType(OAuth2Constants.CLIENT_CREDENTIALS) 
        .resteasyClient(client); 
    return builder.build(); 
} 

// 错误抛出点:无法注入@Normal scoped bean,因为它不可代理(无无参构造器)
@Produces @Dependent @Named("keycloakRealm") 
public RealmRepresentation getKeycloakRealm( 
    @Named("keycloakAdmin") final Keycloak adminClient 
){ 
    return adminClient.realm(resolveKeycloakDeployment().getRealm()).toRepresentation(); 
} 

我的需求是:让Keycloak Bean作为应用级单例(线程安全,仅初始化一次),同时能注入到非应用作用域的Bean中。想知道有没有可行的方案,另外@Singleton作用域能不能解决这个问题,以及它和@ApplicationScoped的区别,还有在EAR或多EAR场景下的适用情况。


可行的解决方案:绕开代理要求

方案1:通过Provider注入目标Bean

CDI中,当你注入javax.inject.Provider<Keycloak>而非直接注入Keycloak时,WELD不需要为Keycloak创建代理——因为Provider本身是CDI可代理的类型,你可以通过provider.get()获取实际的单例实例。修改后的代码如下:

@Produces @Dependent @Named("keycloakRealm") 
public RealmRepresentation getKeycloakRealm( 
    @Named("keycloakAdmin") Provider<Keycloak> adminClientProvider 
){ 
    Keycloak adminClient = adminClientProvider.get();
    return adminClient.realm(resolveKeycloakDeployment().getRealm()).toRepresentation(); 
} 

这种方式简单直接,不需要修改原有的Keycloak生产者逻辑,完全绕开了第三方类无法代理的问题。

方案2:自定义可代理的包装类

如果你不想依赖Provider,也可以自己编写一个符合CDI代理要求的包装类,将第三方Keycloak实例封装进去:

@ApplicationScoped
public class KeycloakAdminWrapper {
    private final Keycloak keycloak;

    @Inject
    public KeycloakAdminWrapper(@Named("keycloakDeployment") KeycloakDeployment deployment) {
        // 复制原有的Keycloak初始化逻辑
        String clientId = deployment.getResourceName(); 
        Map<String, Object> clientCredentials = deployment.getResourceCredentials(); 
        ResteasyClient client = new ResteasyClientBuilder()
            .connectionPoolSize(CONNECTION_POOL_SIZE)
            .maxPooledPerRoute(CONNECTION_POOL_SIZE)
            .defaultProxy("localhost",8888) 
            .build(); 
        this.keycloak = KeycloakBuilder.builder() 
            .clientId(clientId) 
            .clientSecret((String) clientCredentials.get(CredentialRepresentation.SECRET)) 
            .realm(deployment.getRealm()) 
            .serverUrl(deployment.getAuthServerBaseUrl()) 
            .grantType(OAuth2Constants.CLIENT_CREDENTIALS) 
            .resteasyClient(client)
            .build();
    }

    public Keycloak getKeycloak() {
        return keycloak;
    }
}

之后在需要注入的地方使用这个包装类:

@Produces @Dependent @Named("keycloakRealm") 
public RealmRepresentation getKeycloakRealm( 
    KeycloakAdminWrapper adminWrapper 
){ 
    Keycloak adminClient = adminWrapper.getKeycloak();
    return adminClient.realm(resolveKeycloakDeployment().getRealm()).toRepresentation(); 
} 

这个包装类是你自定义的,拥有符合CDI要求的构造器,WELD可以为它生成代理,完美适配第三方类的限制。


@Singleton vs @ApplicationScoped:区别与适用场景

核心差异对比

  1. 作用域范围:

    • @ApplicationScoped(CDI规范):作用域限定为单个Web应用(WAR),如果是包含多个WAR的EAR,每个WAR会拥有独立的@ApplicationScoped实例。
    • @Singleton(EJB规范):作用域是整个Java EE容器实例,同一个容器内的所有WAR/EAR都会共享同一个@Singleton实例。
  2. 并发处理:

    • @ApplicationScoped:默认无容器级并发控制,需要开发者自行保证Bean的线程安全(比如你设置Resteasy连接池的方式)。
    • @Singleton:默认由容器管理并发,默认情况下同一时间只有一个线程能访问Bean方法;你可以通过@Lock(LockType.READ)或@Lock(LockType.WRITE)自定义并发策略。
  3. 代理规则:

    • @ApplicationScoped属于@Normal作用域,要求Bean可代理(需有非私有无参构造器或实现接口)。
    • @Singleton是EJB作用域,其代理机制独立于CDI,无需目标类拥有无参构造器;如果用@EJB注入而非CDI的@Inject,可以完全绕过CDI的代理限制。

用@Singleton解决问题的实践

如果想用@Singleton解决当前问题,可以将Keycloak的初始化逻辑放到EJB Singleton中:

@Singleton
@Named("keycloakAdmin")
public class KeycloakAdminProducer {
    private Keycloak keycloak;

    @PostConstruct
    public void init(@Named("keycloakDeployment") KeycloakDeployment deployment) {
        // Keycloak初始化逻辑
        String clientId = deployment.getResourceName(); 
        Map<String, Object> clientCredentials = deployment.getResourceCredentials(); 
        ResteasyClient client = new ResteasyClientBuilder()
            .connectionPoolSize(CONNECTION_POOL_SIZE)
            .maxPooledPerRoute(CONNECTION_POOL_SIZE)
            .defaultProxy("localhost",8888) 
            .build(); 
        this.keycloak = KeycloakBuilder.builder() 
            .clientId(clientId) 
            .clientSecret((String) clientCredentials.get(CredentialRepresentation.SECRET)) 
            .realm(deployment.getRealm()) 
            .serverUrl(deployment.getAuthServerBaseUrl()) 
            .grantType(OAuth2Constants.CLIENT_CREDENTIALS) 
            .resteasyClient(client)
            .build();
    }

    public Keycloak getKeycloak() {
        return keycloak;
    }
}

然后在@Dependent的生产者中用@EJB注入:

@Produces @Dependent @Named("keycloakRealm") 
public RealmRepresentation getKeycloakRealm( 
    @EJB KeycloakAdminProducer adminProducer 
){ 
    Keycloak adminClient = adminProducer.getKeycloak();
    return adminClient.realm(resolveKeycloakDeployment().getRealm()).toRepresentation(); 
} 

这种方式利用EJB的代理机制,完全避开了CDI对第三方类的代理要求。

EAR/多EAR场景的选择

  • 单WAR或需要每个WAR独立实例:选@ApplicationScoped。
  • EAR包含多个WAR且需要共享实例:选@Singleton,它能保证整个容器内实例唯一。
  • 多EAR场景:每个EAR是独立的容器上下文,每个EAR会拥有自己的@Singleton实例;跨EAR的实例共享需要依赖分布式缓存等额外机制,超出了基础作用域的范畴。

内容的提问来源于stack exchange,提问作者Eric B.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:21:04