I upgraded my SpringBoot 1.3.5 to 1.5.2 RELEASE and am suddenly getting an error when my beans are being wired. I am not doing constructor injection anywhere, so the circular reference makes no sense.
Here is my main class:
#EnableGlobalMethodSecurity(securedEnabled = true)
#SpringBootApplication
public class MyApplication extends WebMvcConfigurerAdapter {
...
}
I have many #Configuration classes in my /src/java/../configuration/ such as
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
...
}
and
#Configuration
#ComponentScan()
public class MyApplicationConfiguration {
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
return new SessionTimeoutEmbeddedServletContainerCustomizer();
}
...
}
I also have a CustomUserDetailsService that does use the passwordEncoder, but its never been a problem prior:
#Component
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private PasswordResetTokenRepository passwordTokenRepository;
...
}
And then I am using Spock for my IntegrationTests by extending
#ContextConfiguration(classes = MyApplication, initializers = ConfigFileApplicationContextInitializer.class)
#WebAppConfiguration
#SpringBootTest
public class BaseSpecification extends Specification {
def setup() {
RestAssured.port = serverPort;
}
#Value('${local.server.port}')
private int serverPort;
}
So all this worked with Spring-Boot 1.3.5 but now that I upgraded I get the following when I build or run tests:
Caused by:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'customUserDetailsService': Unsatisfied
dependency expressed through field 'passwordEncoder'; nested exception
is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'passwordEncoder': Requested bean is
currently in creation: Is there an unresolvable circular reference?
... Caused by:
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'passwordEncoder': Requested bean is
currently in creation: Is there an unresolvable circular reference?
UPDATE
Based upon Andy comment, I moved some dependencies around and think I have it fixed.
Removed this from WebSecurityConfig and put them in MyApplicationConfiguration:
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
return;
}
#Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
While I do not have any constructor injection, it would seem the new Spring Security does it DI differently. I cannot confirm it, but this is no longer an issue.
Related
In a spring boot application who are reactive and use jwt, in my spring-cloud-gateway, I have this code.
#EnableDiscoveryClient
#SpringBootApplication
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SpringSecurityWebFluxConfig {
private final UserServiceImpl userService;
private final JwtTokenUtil tokenUtil;
private static final String[] AUTH_WHITELIST = {
"/resources/**",
"/webjars/**",
"/authorize/**",
"/favicon.ico"};
public SpringSecurityWebFluxConfig(JwtTokenUtil tokenUtil, UserServiceImpl userService) {
this.tokenUtil = tokenUtil;
this.userService = userService;
}
..
}
#Service
public class UserServiceImpl implements ReactiveUserDetailsService, UserService {
private final UserRepository userRepository;
public UserServiceImpl(final UserRepository userRepository) {
this.userRepository = userRepository;
}
...
}
#Repository
public interface UserRepository extends ReactiveCrudRepository<User, Integer>{
}
public class JWTHeadersExchangeMatcher implements ServerWebExchangeMatcher {
#Override
public Mono<MatchResult> matches(final ServerWebExchange exchange) {
}
}
public class JWTReactiveAuthenticationManager implements ReactiveAuthenticationManager {
...
public JWTReactiveAuthenticationManager(final PasswordEncoder passwordEncoder, final UserServiceImpl userService) {
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
}
public class JwtTokenUtil {
...
}
public class TokenAuthenticationConverter implements Function<ServerWebExchange, Mono<Authentication>> {
private final JwtTokenUtil tokenProvider;
public TokenAuthenticationConverter(JwtTokenUtil tokenProvider) {
this.tokenProvider = tokenProvider;
}
}
public class TokenAuthenticationConverter implements Function<ServerWebExchange, Mono<Authentication>> {
private final JwtTokenUtil tokenProvider;
public TokenAuthenticationConverter(JwtTokenUtil tokenProvider) {
this.tokenProvider = tokenProvider;
}
}
[ main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling
refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'springSecurityWebFluxConfig' defined in
file
[/home/mac/Development/project/reactive-cloud/gateway-service/build/classes/java/main/com/example/gatewayservice/config/SpringSecurityWebFluxConfig.class]:
Unsatisfied dependency expressed through constructor parameter 1;
nested exception is
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'userServiceImpl' defined in file
[/home/mac/Development/project/reactive-cloud/gateway-service/build/classes/java/main/com/example/gatewayservice/service/UserServiceImpl.class]:
Unsatisfied dependency expressed through constructor parameter 0;
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type
'com.example.gatewayservice.repository.UserRepository' available:
expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations: {} 2019-06-27 11:47:23.336 INFO 53073 --- [
main] ConditionEvaluationReportLoggingListener :
Tried to put autowired
on UserServiceImpl in SpringSecurityWebFluxConfig class
on UserRepository in UserServiceImpl class
on UserServiceImpl in JWTReactiveAuthenticationManager.class
but get same error
Edit
if i use
#EnableR2dbcRepositories to GatewayServiceApplication, I dont't have this error but it search about DatabaseClient
Please add #Autowired annotation in UserServiceImpl class for UserRepository variable in constitute constructor or variable level.
I'm seeing the following message on a Spring Boot app startup:
> *************************** 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
I'm wondering what's causing the duplication of that bean and how one can remove it without the necessity of using the #Primary annotation? Not sure where the kai-com package(?) from the above is coming from.
Here's the bean in question:
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="";
}
}
and the app/config, etc.:
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 too seems to be only aware of a single instance of the bean:
When using #EnableConfigurationProperties with #ConfigurationProperties you will get a bean named <prefix>-<fqn>, the kai-com.example.authservice.config.OAuthProperties. (See also the reference guide).
When the #ConfigurationProperties bean is registered that way, the bean has a conventional name: <prefix>-<fqn>, where <prefix> is the environment key prefix specified in the #ConfigurationProperties annotation and <fqn> is the fully qualified name of the bean. If the annotation does not provide any prefix, only the fully qualified name of the bean is used.
The bean name in the example above is acme-com.example.AcmeProperties. (From the Reference Guide).
The #Component will lead to another registration of the bean with the regular name of the classname with a lowercase character. The other instance of your properties.
the #EnableConfigurationProperties annotation is also automatically applied to your project so that any existing bean annotated with #ConfigurationProperties is configured from the Environment. You could shortcut MyConfiguration by making sure AcmeProperties is already a bean, as shown in the following example: (From the Reference Guide).
The key here is that #EnableConfigurationProperties is already globally applied and processes any bean annotated with #ConfigurationProperties.
So basically you where mixing the 2 ways of using #ConfigurationProperties and Spring Boot 2 now prevents that misuse. This way you write better code (and reduce the memory footprint and performance slightly).
So either remove the #Component or remove the #EnableConfigurationProperties, either way will work.
The following change (removing of #EnableConfigurationProperties) seems to help relieving the need for the #Primary annotation:
#Configuration
#EnableAuthorizationServer
//#EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired private OAuthProperties oauthProps;
Perhaps someone can describe the internal Spring mechanics of secondary bean creation (and its namespace/package assignment) by that annotation which seemingly causes the collision with the #Autowired one, or point me to the appropriate documentation of this behavior.
In my Spring Boot 2.0.2 application I am trying to inject a component herited from abstract class which implements an interface and it doesn't work.
Code :
Component Abstract : (Do I need to put #Component ?)
package app.project.service;
#Component
public abstract class AbstractStepService implements IStepService {
protected final void addTask() {
...
}
#Override
public StepDataDto launch() throws StepException {
...
}
}
Interface :
package app.project.service;
public interface IStepService {
StepDataDto launch() throws StepException;
}
package app.project.service;
Component :
#Component
public class CStepServiceImpl extends AbstractStepService implements IStepService {
#PostConstruct
private void defineTasks() {
}
}
package app.project.service;
Junit Test :
#RunWith(SpringRunner.class)
#SpringBootTest
public class CStepServiceTest {
#Autowired
#Qualifier("cStepServiceImpl")
private IStepService service;
}
package app.project;
Application :
#SpringBootApplication
#ComponentScan(basePackages ={"app.project.service"})
public class MyApplication {}
Error message when launching my Junit test :
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'app.project.service.IStepService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true), #org.springframework.beans.factory.annotation.Qualifier(value=cStepServiceImpl)}
Any idea ?
Thanks
Change your declaration like this
#Autowired
#Qualifier("CStepServiceImpl")
private IStepService service;
or
#Autowired
private IStepService CStepServiceImpl;
That should work. The bean name created automatically by spring has CStepServiceImpl name.
You can also name your bean like this
#Component(value = "myName")
public class CStepServiceImpl extends AbstractStepService implements IStepService {
}
and use myName during Autowiring.
I am trying to disable real Mongo connection and replace it with Fongo mock in tests.
Here is my test class:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ControllerTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private MyService service;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void performTest() throws Exception {
... logic ...
}
}
It works fine unless I try to add my configuration file changing this line:
#SpringBootTest
to this:
#SpringBootTest(classes = TestConfig.class)
config class itself:
#Configuration
#ComponentScan
#EnableMongoRepositories
public class TestConfig extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "FongoDB";
}
#Override
public Mongo mongo() {
return new Fongo(getDatabaseName()).getMongo();
}
}
Then application fails to find beans and throws the next exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1486)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 28 more
How can I fix it and apply additional configuration properly?
try use
#SpringBootTest
#Import(value = TestConfig.class)
instead of
#SpringBootTest(classes = TestConfig.class)
keep #SpringBootTest and then create a class using #TestConfiguration with the beans in as follows:
#TestConfiguration
public class TransactionManagerTestConfiguration {
#Bean
public String getDatabaseName() {
return "FongoDB";
}
#Bean
public Mongo mongo() {
return new Fongo(getDatabaseName()).getMongo();
}
}
As per the javadoc: Configuration that can be used to define additional beans or customizations for a test. Unlike regular Configuration classes the use of TestConfiguration does not prevent auto-detection of SpringBootConfiguration.
I have a spring boot 2.0.0 M2 application who run well.
I use autowired on constructor
#RequestMapping(value = "/rest")
#RestController
public class AddressRestController extends BaseController{
private final AddressService AddressService;
#Autowired
public AddressRestController(final AddressService AddressService) {
this.AddressService = AddressService;
}
...
}
#Service
public class AddressServiceImpl extends BaseService implements AddressService {
#Autowired
public AddressServiceImpl(final AddressRepository AddressRepository) {
this.AddressRepository = AddressRepository;
}
private final AddressRepository AddressRepository;
...
}
public interface AddressRepository extends JpaRepository<Address, Integer>, AddressRepositoryCustom {
}
#Repository
public class AddressRepositoryImpl extends SimpleJpaRepository implements AddressRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Autowired
public AddressRepositoryImpl(EntityManager em) {
super(Address.class, em);
}
...
}
When i try to run a basic test
#RunWith(SpringJUnit4ClassRunner.class)
public class AddressServiceTest {
#Autowired
private AddressService service;
#MockBean
private AddressRepository restTemplate;
#Test
public void getAddress(){
MockitoAnnotations.initMocks(this);
Pageable page = PageRequest.of(0, 20);
Page<Address> pageAdr = mock(Page.class);
given(this.restTemplate.findAll(page)).willReturn(pageAdr);
Page<AddressDto> pageDto = service.getAddress(page);
}
}
I get this error
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name
'com.sonos.arcor.service.AddressServiceTest': Unsatisfied dependency
expressed through field 'service'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type ''com.sonos.arcor.service.AddressService'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
I don't understand why i get this error.
You need to annotate the test with SpringBootTest so that spring initialize an application context
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications
#SpringBootTest
#RunWith(SpringJUnit4ClassRunner.class)
public class AddressServiceTest {
// the remaining test
}
Also you do not need MockitoAnnotations.initMocks(this);
Spring takes care of the mock handling
When [#MockBean is]used on a field, the instance of the created mock will also be
injected. Mock beans are automatically reset after each test method
see Mocking and spying beans