【问题标题】:Why I obtain this Spring Exception No qualifying bean of type is defined expected single matching bean but found 2?为什么我得到这个 Spring 异常没有定义类型的限定 bean 预期的单个匹配 bean 但找到 2?
【发布时间】:2015-01-28 04:23:52
【问题描述】:

我正在学习Spring Core认证,我有以下疑问:

当我执行 JUnit 测试方法时,我收到此错误消息(提供的用户指南预计会出现此错误,然后解释如何修复它):

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [rewards.internal.account.AccountRepository] is defined: expected single matching bean but found 2: stubAccountRepository,jdbcAccountRepository
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:970)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:858)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:811)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:739)
    ... 43 more

据我了解,这是因为 Spring 发现了多个相同类型的 bean:stubAccountRepositoryjdbcAccountRepository

这是 StubAccountRepository 类:

/**
 * A dummy account repository implementation. Has a single Account
 * "Keith and Keri Donald" with two beneficiaries "Annabelle" (50% allocation)
 * and "Corgan" (50% allocation) associated with credit card "1234123412341234".
 * 
 * Stubs facilitate unit testing. An object needing an AccountRepository can
 * work with this stub and not have to bring in expensive and/or complex
 * dependencies such as a Database. Simple unit tests can then verify object
 * behavior by considering the state of this stub.
 */
@Repository
public class StubAccountRepository implements AccountRepository {

    private Logger logger = Logger.getLogger(StubAccountRepository.class);

    private Map<String, Account> accountsByCreditCard = new HashMap<String, Account>();

    /**
     * Creates a single test account with two beneficiaries. Also logs creation
     * so we know which repository we are using.
     */
    public StubAccountRepository() {
        logger.info("Creating " + getClass().getSimpleName());
        Account account = new Account("123456789", "Keith and Keri Donald");
        account.addBeneficiary("Annabelle", Percentage.valueOf("50%"));
        account.addBeneficiary("Corgan", Percentage.valueOf("50%"));
        accountsByCreditCard.put("1234123412341234", account);
    }

    public Account findByCreditCard(String creditCardNumber) {
        Account account = accountsByCreditCard.get(creditCardNumber);
        if (account == null) {
            throw new EmptyResultDataAccessException(1);
        }
        return account;
    }

    public void updateBeneficiaries(Account account) {
        // nothing to do, everything is in memory
    }
}

这是 **JdbcAccountRepository **:

/** * 使用 JDBC API 从数据源加载帐户。 */ @Repository 公共类 JdbcAccountRepository 实现 AccountRepository {

private Logger logger = Logger.getLogger(JdbcAccountRepository.class);

private DataSource dataSource;

/**
 * Constructor logs creation so we know which repository we are using.
 */
public JdbcAccountRepository() {
    logger.info("Creating " + getClass().getSimpleName());
}

/**
 * Sets the data source this repository will use to load accounts.
 * @param dataSource the data source
 */
@Autowired
public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

public Account findByCreditCard(String creditCardNumber) {
    String sql = "select a.ID as ID, a.NUMBER as ACCOUNT_NUMBER, a.NAME as ACCOUNT_NAME, c.NUMBER as CREDIT_CARD_NUMBER, b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS from T_ACCOUNT a, T_ACCOUNT_BENEFICIARY b, T_ACCOUNT_CREDIT_CARD c where ID = b.ACCOUNT_ID and ID = c.ACCOUNT_ID and c.NUMBER = ?";
    Account account = null;
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = dataSource.getConnection();
        ps = conn.prepareStatement(sql);
        ps.setString(1, creditCardNumber);
        rs = ps.executeQuery();
        account = mapAccount(rs);
    } catch (SQLException e) {
        throw new RuntimeException("SQL exception occurred finding by credit card number", e);
    } finally {
        if (rs != null) {
            try {
                // Close to prevent database cursor exhaustion
                rs.close();
            } catch (SQLException ex) {
            }
        }
        if (ps != null) {
            try {
                // Close to prevent database cursor exhaustion
                ps.close();
            } catch (SQLException ex) {
            }
        }
        if (conn != null) {
            try {
                // Close to prevent database connection exhaustion
                conn.close();
            } catch (SQLException ex) {
            }
        }
    }
    return account;
}

public void updateBeneficiaries(Account account) {
    String sql = "update T_ACCOUNT_BENEFICIARY SET SAVINGS = ? where ACCOUNT_ID = ? and NAME = ?";
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        conn = dataSource.getConnection();
        ps = conn.prepareStatement(sql);
        for (Beneficiary beneficiary : account.getBeneficiaries()) {
            ps.setBigDecimal(1, beneficiary.getSavings().asBigDecimal());
            ps.setLong(2, account.getEntityId());
            ps.setString(3, beneficiary.getName());
            ps.executeUpdate();
        }
    } catch (SQLException e) {
        throw new RuntimeException("SQL exception occurred updating beneficiary savings", e);
    } finally {
        if (ps != null) {
            try {
                // Close to prevent database cursor exhaustion
                ps.close();
            } catch (SQLException ex) {
            }
        }
        if (conn != null) {
            try {
                // Close to prevent database connection exhaustion
                conn.close();
            } catch (SQLException ex) {
            }
        }
    }
}

/**
 * Map the rows returned from the join of T_ACCOUNT and T_ACCOUNT_BENEFICIARY to an fully-reconstituted Account
 * aggregate.
 * @param rs the set of rows returned from the query
 * @return the mapped Account aggregate
 * @throws SQLException an exception occurred extracting data from the result set
 */
private Account mapAccount(ResultSet rs) throws SQLException {
    Account account = null;
    while (rs.next()) {
        if (account == null) {
            String number = rs.getString("ACCOUNT_NUMBER");
            String name = rs.getString("ACCOUNT_NAME");
            account = new Account(number, name);
            // set internal entity identifier (primary key)
            account.setEntityId(rs.getLong("ID"));
        }
        account.restoreBeneficiary(mapBeneficiary(rs));
    }
    if (account == null) {
        // no rows returned - throw an empty result exception
        throw new EmptyResultDataAccessException(1);
    }
    return account;
}

/**
 * Maps the beneficiary columns in a single row to an AllocatedBeneficiary object.
 * @param rs the result set with its cursor positioned at the current row
 * @return an allocated beneficiary
 * @throws SQLException an exception occurred extracting data from the result set
 */
private Beneficiary mapBeneficiary(ResultSet rs) throws SQLException {
    String name = rs.getString("BENEFICIARY_NAME");
    MonetaryAmount savings = MonetaryAmount.valueOf(rs.getString("BENEFICIARY_SAVINGS"));
    Percentage allocationPercentage = Percentage.valueOf(rs.getString("BENEFICIARY_ALLOCATION_PERCENTAGE"));
    return new Beneficiary(name, allocationPercentage, savings);
}

}

究竟取决于前面的错误信息?

我认为这是在应用程序启动时发生的配置问题。对吗?

我认为这是因为这两个类都使用 @RepositoryStubAccountRepositoryJdbcAccountRepository)进行了注释,并实现了AccountRepository 接口,因此 Spring 无法知道哪个类用作 AccountRepository 的实现存储库。

我的推理是正确的还是我遗漏了什么?你能就这种情况给我更多的解释吗?

Tnx

【问题讨论】:

  • 是的,正确,您应该指定 bean 名称并使用 Qualifaer 注释;)

标签: java spring


【解决方案1】:

好吧,Maksym 已经告诉过你了,但为了让你更清楚,我会尽量详细一点。

您有 2 个“AccountRepository”接口实现:StubAccountRepository 和 JdbcAccountRepository。当您在类型“AccountRepository”上使用@Autowired 注释时,您只需告诉 Spring 执行“按类型自动装配”。因此 Spring 检查应该自动装配的字段的类型并尝试定位该类型的 SINGLE bean(就好像您调用了 ApplicationContext.getBean(AccountRepository.class))。由于无法找到该类型的 SINGLE bean,因此 ApplicationContext 无法启动。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 2015-11-13
    • 2014-02-10
    相关资源
    最近更新 更多