spring - Spring Boot 应用程序需要一个带有 @Primary 注释的 bean 才能启动
问题描述
我在 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
我想知道是什么导致了该 bean 的重复,以及如何在不需要使用@Primary
注释的情况下将其删除?不确定上面的kai-com包(?)来自哪里。
这是有问题的bean:
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 的一个实例:
解决方案
@EnableConfigurationProperties
与你一起使用时@ConfigurationProperties
会得到一个名为 的<prefix>-<fqn>
bean kai-com.example.authservice.config.OAuthProperties
,. (另请参阅参考指南)。
当
@ConfigurationProperties
bean 以这种方式注册时,bean 有一个约定名称:<prefix>-<fqn>
,其中<prefix>
是@ConfigurationProperties
注释中指定的环境键前缀,并且<fqn>
是 bean 的完全限定名称。如果注解不提供任何前缀,则仅使用 bean 的完全限定名称。上面示例中的 bean 名称是 acme-com.example.AcmeProperties。(来自参考指南)。
这@Component
将导致使用带有小写字符的类名的常规名称再次注册 bean。您的属性的另一个实例。
注释也会自动应用于您的
@EnableConfigurationProperties
项目,以便任何现有的带有注释的 bean@ConfigurationProperties
都从Environment
.MyConfiguration
您可以通过确保已经是一个 bean 来实现快捷方式AcmeProperties
,如下例所示:(来自参考指南)。
这里的关键是它@EnableConfigurationProperties
已经被全局应用并处理任何用@ConfigurationProperties
.
所以基本上你现在混合两种使用方式@ConfigurationProperties
和 Spring Boot 2 可以防止这种误用。这样您可以编写更好的代码(并稍微减少内存占用和性能)。
所以要么删除 要么@Component
删除@EnableConfigurationProperties
,任何一种方式都可以。
推荐阅读
- sql - SQL:对列中的值执行算术运算
- python - 实例化大型稀疏矩阵以进行赋值操作
- php - 带有@转义字符的奇怪 Laravel Blade 渲染
- php - 通过 [i][1] 键合并多维 PHP 数组,然后将顶级索引重新定义为该键
- sql-server - 如何将此更新代码与以下代码合并?
- php - 无法在 PHP 中修改 JSON 文件
- colors - 让 ImageMagick 将所有颜色输出为十六进制
- angular - Angular Modal 没有正确显示阴影
- php - 嵌入式视频未在 Apple 设备中加载
- android - 如何从另一个 Activity 中的 onActivityResult 更改 Fragment 中的视图?