Spring Boot应用程序需要一个带有@Primary注释的bean才能启动

时间:2018-11-06 00:51:45

标签: spring spring-boot

在Spring Boot应用启动时,我看到以下消息:

> *************************** APPLICATION FAILED TO START
> ***************************
> 
> Description:
> 
> Field oauthProps in com.example.authservice.AuthorizationServerConfig
> required a single bean, but 2 were found:
>   - OAuthProperties: defined in file [/Users/simeonleyzerzon/abc/spring-security/spring-security-5-oauth-client/auth-service/target/classes/com/example/authservice/config/OAuthProperties.class]
>   - kai-com.example.authservice.config.OAuthProperties: defined in null
> 
> 
> Action:
> 
> Consider marking one of the beans as @Primary, updating the consumer
> to accept multiple beans, or using @Qualifier to identify the bean
> that should be consumed

我想知道是什么原因导致了该豆的重复,以及在没有使用@Primary注释的情况下如何将其删除?不确定上面的 kai-com 软件包(?)来自何处。

这是有问题的豆子:

package com.example.authservice.config;

    //@Primary
    @Component
    @ConfigurationProperties(prefix="kai")
    @Setter @Getter
    public class OAuthProperties {


        private String[] redirectUris;


        private String clientId;


        private String clientSecret;

        private final Token token = new Token();


        @Setter @Getter
        public static class Token{

            private String value;

            private String type="";

        }

    }

和应用程序/配置等:

package com.example.authservice;

import ...
@SpringBootApplication
public class AuthServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthServiceApplication.class, args);
    }
}

@Controller
class MainController {

    @GetMapping("/")
    String index() {
        return "index";
    }
}

@RestController
class ProfileRestController {

    @GetMapping("/resources/userinfo")
    Map<String, String> profile(Principal principal) {
        return Collections.singletonMap("name", principal.getName());
    }
}

@Configuration
@EnableResourceServer
class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/resources/**")
                .authorizeRequests()
                .mvcMatchers("/resources/userinfo").access("#oauth2.hasScope('profile')");
    }
}

@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {



    @Autowired private OAuthProperties oauthProps;

    private final AuthenticationManager authenticationManager;

    AuthorizationServerConfig(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients
            .inMemory()

                .withClient(oauthProps.getClientId())
                .secret(oauthProps.getClientSecret())
                .authorizedGrantTypes("authorization_code")
                .scopes("profile")
                .redirectUris(oauthProps.getRedirectUris());


    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.authenticationManager(this.authenticationManager);

        if (oauthProps.getToken().getType().equals("jwt")) {
            endpoints.tokenStore(this.tokenStore()).accessTokenConverter(jwtAccessTokenConverter());
        }else {
            endpoints.tokenEnhancer(eapiTokenEnhancer());
        }
    }

    TokenEnhancer eapiTokenEnhancer() {

        return new TokenEnhancer() {

            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {

                DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
                result.setValue(oauthProps.getToken().getValue());
                return result;
            }
        };

    }

    @Bean
    JwtAccessTokenConverter jwtAccessTokenConverter() {
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource(".keystore-oauth2-demo"), //keystore
                "admin1234".toCharArray());                                                                 //storepass
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(factory.getKeyPair("oauth2-demo-key"));                          //alias
        return jwtAccessTokenConverter;
    }

    @Bean
    TokenStore tokenStore() {
        return new JwtTokenStore(this.jwtAccessTokenConverter());
    }
}

@Service
class SimpleUserDetailsService implements UserDetailsService {

    private final Map<String, UserDetails> users = new ConcurrentHashMap<>();

    SimpleUserDetailsService() {
        Arrays.asList("josh", "rob", "joe")
                .forEach(username -> this.users.putIfAbsent(
                        username, new User(username, "pw", true, true, true, true, AuthorityUtils.createAuthorityList("USER","ACTUATOR"))));
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return this.users.get(username);
    }
}

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin();

    }
}

Eclipse似乎也只知道该bean的单个实例:

enter image description here

2 个答案:

答案 0 :(得分:4)

@EnableConfigurationProperties@ConfigurationProperties一起使用时,您将获得一个名为<prefix>-<fqn>的bean,kai-com.example.authservice.config.OAuthProperties。 (另请参见the reference guide)。

  

以这种方式注册@ConfigurationProperties bean时,该bean具有常规名称:<prefix>-<fqn>,其中<prefix>@ConfigurationProperties批注中指定的环境键前缀,并且<fqn>是bean的全限定名。如果注释不提供任何前缀,则仅使用Bean的完全限定名称。   上例中的bean名称是acme-com.example.AcmeProperties。 (来自参考指南)。

@Component将导致使用常规类名的小写字符再次注册Bean。属性的另一个实例。

  

@EnableConfigurationProperties注释也将自动应用于您的项目,以便从@ConfigurationProperties配置任何用Environment注释的现有bean。您可以通过确保MyConfiguration已经是bean来简化AcmeProperties的操作,如以下示例所示:(来自参考指南)。

此处的关键是@EnableConfigurationProperties已被全局应用,并处理任何带有@ConfigurationProperties注释的bean。

因此,基本上,您现在可以结合使用@ConfigurationProperties和Spring Boot 2的两种方式来防止这种滥用。这样,您可以编写更好的代码(并稍微减少内存占用和性能)。

因此,删除@Component或删除@EnableConfigurationProperties都可以。

答案 1 :(得分:0)

以下更改(删除了@EnableConfigurationProperties)似乎有助于减轻对@Primary注释的需要:

@Configuration
@EnableAuthorizationServer
//@EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired private OAuthProperties oauthProps;

也许有人可以通过该注释来描述辅助bean创建(及其名称空间/程序包分配)的内部Spring机制,该注释似乎导致与@Autowired的冲突,或者使我指向此行为的适当文档。