Spring Boot Testing - No Qualifying Bean Exception - java

While attempting some tests using the Spring Boot framework, I am running into an issue with finding a Bean that the test unit depends on.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authServerApplication': Unsatisfied dependency expressed through field 'passwordEncoder'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.password.PasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
My test class:
#RunWith(SpringRunner.class)
#DataJpaTest
#SpringBootTest
public class UserDetailsTest {
#Autowired
private TestEntityManager entityManager;
// #MockBean
// private PasswordEncoder passwordEncoder;
#Autowired
private UserRepository userRepo;
#Test
public void test() {
OAuthUser user = null;
this.entityManager.persist(new OAuthUser("Kelly", "Marchewa", "kmarchewa", "password"));
user = userRepo.findByUserName("kmarchewa");
System.out.println(user.getPassword());
assertThat(user.getUserName()).isEqualTo("kmarchewa");
}
}
If I uncomment the #MockBean portion, the code will compile fine. However, I want to test the repository on its ability to encode and decode passwords too. To my understanding of the documentation, the #SpringBootTest annotation should be able to automatically "pick-up" the #Configuration classes. I have a main #SpringBootApplication:
#SpringBootApplication
public class AuthServerApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
#Bean
public CommandLineRunner demo(UserRepository repository) {
return(args) -> {
OAuthUser user = new OAuthUser();
user.setFirstName("Kelly");
user.setLastName("Marchewa");
user.setPassword(passwordEncoder.encode("Admin"));
user.setUserName("Admin");
// repository.save(user);
};
}
}
This Spring Boot Application depends on three other #Configuration classes: AppConfig, SecurityConfig, and AuthServerConfig. For this issue, the SecurityConfig and AppConfig classes are relevant (they include references to the PasswordEncoder bean).
AppConfig (partial)
#Configuration
public class AppConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.datasource.driverClassName}")
private String dbDriverClassName;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/// more code here
}
SecurityConfig:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuthUserDetailsService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// Hash password
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName("test")
.and()
.csrf()
.disable();
}
}
The UserRepository class is very simple:
public interface UserRepository extends CrudRepository<OAuthUser, Long> {
public OAuthUser findByUserName(String name);
}
How can I ensure all required beans are found for my tests?
Thanks.
EDIT:
I receive the same error if I attempt to #Autowire the bean in my test class.
#Autowired
private PasswordEncoder passwordEncoder;

The problem is #DataJpaTest this annotation should be used only for Data repositories test and not full integration (which is what you are doing) as because it only persistence beans are created in context and not all you beans (the reason bean can not be found). What you need to do is use only #SpringBootTest and declared h2 as testing dependency, in that way a full recreation of your application will be created using in memory database

Related

Error creating bean with name 'securityConfiguration' when running test

I'm creating a Spring Boot application with Spring Security. I've created a custom login page that uses an auth endpoint in our AuthController. My SecurityConfiguration class extends WebSecurityConfigurerAdapter.
At the moment, I'm trying to create tests for this without any results. Most of this is new to me and I'm still learning, specially testing. I'm not even sure if I've done this right at all.
Any help is appreciated.
SECURITYCONFIGURATION
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService applicationUserService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers( "/register", "/index*", "/static/**", "/*.js", "/*.json", "/*.ico").permitAll()
.antMatchers(HttpMethod.POST,"/api/auth/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
//.loginProcessingUrl("/api/auth/authenticate")
.defaultSuccessUrl("/home", true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(applicationUserService);
return provider;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
AUTHCONTROLLER
#RequiredArgsConstructor
#RestController
#RequestMapping("api/auth/")
public class AuthController {
private final UserService userService;
#PostMapping("authenticate")
public ResponseEntity<?> verifyUser(#RequestBody User user) throws Exception {
return userService.authenticateUser(user);
}
}
USERSERVICE
#Service
#RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final AuthenticationManager authenticationManager;
public ResponseEntity<?> authenticateUser(User user) throws Exception {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getEmail(),user.getPassword()));
if (!authentication.isAuthenticated())
return new ResponseEntity<>(new ResponseObject("Error"), HttpStatus.UNAUTHORIZED);
else {
SecurityContextHolder.getContext().setAuthentication(authentication);
return new ResponseEntity<>(user,HttpStatus.OK);
}
}
}
TESTCLASS
#WebMvcTest(AuthController.class)
class AuthControllerTest {
User user;
#MockBean
UserService userService;
#Autowired
MockMvc mockMvc;
#BeforeEach
void setUp() {
user = User.builder()
.email("test#hotmail.com")
.password("password")
.id(1L).build();
}
#Test
void verifyUser() throws Exception {
given(userService.authenticateUser(any(User.class))).willReturn(new ResponseEntity<>(HttpStatus.OK));
mockMvc.perform(post("/api/auth/authenticate")
.contentType(MediaType.APPLICATION_JSON).content(new ObjectMapper().writeValueAsString(user)))
.andExpect(status().isOk());
}
}
ERRORS
Failed to load ApplicationContext java.lang.IllegalStateException: Failed to load ApplicationContext Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfiguration' defined in file [\SecurityConfiguration.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.password.PasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.password.PasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Spring security test with userDetailsService gives illegalStateException in test case

I have some spring controller tests that worked fine before.
I recently added authentication using a userDetailsService, and now when i run controller tests it says this:
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'nl.kars.lms.service.MyUserDetailsService' available:
expected at least 1 bean which qualifies as autowire candidate.
I don't understand why, because everything should be configured correctly. It only happens when running the controller tests, running the app works perfectly fine. Here are my classes.
Test case
#RunWith(SpringRunner.class)
#WebMvcTest(ActionController.class)
public class ActionControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private ActionService service;
#Test
public void testGetActions_returns_result_from_service() throws Exception {
int actionId = 1;
Action action = new Action();
action.setId(actionId);
List<Action> actionsList = Arrays.asList(action);
given(service.getActions()).willReturn(actionsList);
mvc.perform(get("/actions")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].id", Matchers.is(actionId)));
}
}
Configuration
#EnableWebSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
http.csrf().disable()
.authorizeRequests()
.antMatchers("/**")
.fullyAuthenticated()
.and().httpBasic();
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
UserDetailsService
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private EmployeeService employeeService;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return new EmployeeDetails(employeeService.getEmployeeByEmail(email));
}
}
My question is, how do i stop the error from occuring? What am i doing wrong? I am lost at this point.
Thanks
Since you have named Your MyUserDetailsService as userDetailsService in #Service("userDetailsService") you have two options
First One :
Use #Qualifier("userDetailsService") in SecurityConfiguration.
Second Option: Autowire UserDetailsService instead of MyUserDetailsService in SecurityConfiguration.
I suggest you try the first option
#Autowired
#Qualifier("userDetailsService")
MyUserDetailsService userDetailsService;

Spring mvc hide one package from others

As it can be seen in picutre, my ComponentScan is set to whole project. But somehow 'config' package doesn't see any other package and vice versa.
If I run my project, i get an error on startup that No qualifying bean of type 'com.rjproject.dao.UserDao' available, and the same for all other beans, that autowired in this package. But they autowiring perfectly fine in all other packages.
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through field 'myUserDetailsService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.rjproject.service.MyUserDetailsService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
But this bean exists and autowires fine in any other package.
here webSecurityConfig
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
MyUserDetailsService myUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**")
.access("hasRole('ADMIN')").and().formLogin()
.loginPage("/login").failureUrl("/login?error")
.usernameParameter("username")
.passwordParameter("password")
.and().logout().logoutSuccessUrl("/login?logout")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/403");
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
and here MyUserDetailService
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username)
throws UsernameNotFoundException {
com.rjproject.entities.User user = userDao.findByUserName(username);
List<GrantedAuthority> authorities =
buildUserAuthority(user.getAuthorities());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.rjproject.entities.User user,
List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(),
user.isEnabled(), true, true, true, authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<Authorities> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (Authorities userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getAuthority()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
}
UPD
As i figured out, the problem is that spring beans doesn't created already when i want to autowire it in config. But every online example says that it should work.
It doesn't hide. You just need to set #ComponentScan annotation to all of your config classes. Each config class should have its own #ComponentScan

Spring #Configuration not overriden in test context

Today I updated my project from Spring Boot 1.5.9 to 2.1.1, and some of my tests stopped working. When i start the tests, error pops on console:
Field authEntryPoint in com.example.rest.config.SecurityConfig required a bean of type 'com.example.rest.service.auth.entrypoints.AuthenticationEntryPoint' that could not be found.
The problem is I have bean of this type defined in my SecurityConfig class, but I am overriding this configuration in my test package in TestApplication class. Security config is defined there as static inner class. I have tried different approaches including Spring profiles and #Primary annotation, but nothing seems to work and Spring doesn't pick my test configuration like it did before. Only thing that worked was when I deleted the non-test version of SecurityConfig class and test version became only bean of this type.
Can someone tell me how do I override this original configuration or how to turn off Spring Security just for testing? Or maybe there is a way to force Spring not to pick up that non-test #Configuration bean?
SecurityConfig.class
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationEntryPoint authEntryPoint;
#Autowired
BasicAuthenticationProvider basicAuthProvider;
#Autowired
PreAuthenticatedUserDetailsService preAuthUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/rest/query/id/*/user/*",
"/rest/files/**/*").hasAnyRole("CLIENT", "SYSTEM")
.antMatchers("/public/api/management/**/*").hasRole("SYSTEM")
.antMatchers("/public/api/**/*").hasAnyRole("SYSTEM", "USER")
.antMatchers("/rest/**/*").hasRole("SYSTEM")
.and()
.x509()
.userDetailsService(preAuthUserDetailsService)
.and()
.httpBasic()
.authenticationEntryPoint(authEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(basicAuthProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/").antMatchers("/rest/files/name/**");
}
}
Test SpringBootClass with SecurityConfig inside
#SpringBootApplication
public class TestApplication {
#Configuration
#EnableWebSecurity
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll()
.and().csrf().disable();
}
}
}
Example test from the suite
#RunWith(SpringRunner.class)
#WebMvcTest(DocumentManagementController.class)
public class DocumentManagementControllerTests {
#Autowired
MockMvc mvc;
#MockBean
SystemMetadataService systemMetadataService;
#MockBean
CustomMetadataService customMetadataService;
#MockBean
PrinterService printerService;
#MockBean
EventLoggerService eventLoggerService;
#Captor ArgumentCaptor<String> systemCaptor;
#Captor ArgumentCaptor<String> clientCaptor;
#Captor ArgumentCaptor<Boolean> holdCaptor;
#Captor ArgumentCaptor<String> retentionCaptor;
#Captor ArgumentCaptor<String> objectPathCaptor;
#Captor ArgumentCaptor<Boolean> accessCaptor;
#Captor ArgumentCaptor<Boolean> manualProcessingCaptor;
#Captor ArgumentCaptor<Boolean> incorrectCaptor;
#Captor ArgumentCaptor<Integer> statusCaptor;
#Captor ArgumentCaptor<Boolean> noTemplateCaptor;
#Test
public void setDocumentAccess_givenProperData_shouldReturnOk() throws Exception {
when(customMetadataService.setDocumentAccess(anyString(), anyBoolean()))
.then(inv -> new HcpCreateObjectResult(inv.getArgument(0)));
Boolean accessForbidden = true; String objectPath = "path";
mvc.perform(get("/rest/management/access/forbid/"+accessForbidden+"?objectPath="+objectPath))
.andExpect(status().isOk());
verify(customMetadataService).setDocumentAccess(objectPathCaptor.capture(), accessCaptor.capture());
assertThat(objectPathCaptor.getValue(), is(equalTo(objectPath)));
assertThat(accessCaptor.getValue(), is(equalTo(accessForbidden)));
}
I managed to do make this work using #Profile and #ActiveProfiles. But i had to extract my static inner #Configuration class to another java file and then it automagically started to work. Still haven't found why it worked in earlier version of Spring Boot

Unresolved circular reference when trying to deploy a spring boot war to tomcat8?

I am new to spring and trying to create a web app using spring boot and jsp which I can deploy using tomcat8 on a raspberry pi. I can deploy my app through sts on an embedded tomcat instance and I can also deploy a war file to Jenkins without any errors. However, when I add the war to tomcat8 webapps folder and start tomcat I get the following error:
2016-04-19 10:54:41.384 WARN 5525 --- [ost-startStop-1] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.rcctv.controllers.UserController.setUserService(com.rcctv.services.UserService); nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userServiceImpl' defined in file [/usr/share/tomcat8/webapps/RaspberryCCTV-0.0.1-SNAPSHOT/WEB-INF/classes/com/rcctv/services/UserServiceImpl.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.security.crypto.password.PasswordEncoder]: : Error creating bean with name 'webSecurityConfig': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'webSecurityConfig': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?
I have tried annotating my configuration class with #Lazy and added setter methods to my userServiceImpl class but I still got the issue. Any help would be greatly appreciated?
webConfig class
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${rememberMe.privateKey}")
private String rememberMeKey;
#Value("${spring.profiles.active}")
private String env;
#Resource
private UserDetailsService userService;
#Bean
public HibernateJpaSessionFactoryBean sessionFactory() {
return new HibernateJpaSessionFactoryBean();
}
#Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices(rememberMeKey, userService);
return rememberMeServices;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/",
"/home",
"/error",
"/signup",
"/forgot-password",
"/reset-password/*",
"/public/**",
"/users/*").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/raspberrycctv")
.permitAll().and()
.rememberMe().key(rememberMeKey).rememberMeServices(rememberMeServices()).and()
.logout()
.permitAll();
if (!env.equals("dev"))
http.requiresChannel().anyRequest().requiresSecure();
}
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
}
UserSeviceImpl
#Service
#Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public class UserServiceImpl implements UserService, UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
private MailSender mailSender;
#Autowired
public UserServiceImpl(UserRepository userRepository,
PasswordEncoder passwordEncoder,
MailSender mailSender) {
this.mailSender = mailSender;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Autowired
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#Autowired
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
#Override
#Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void signup(SignupForm signupForm) {
final User user = new User();
user.setEmail(signupForm.getEmail());
user.setName(signupForm.getName());
user.setPassword(passwordEncoder.encode(signupForm.getPassword()));
user.getRoles().add(Role.UNVERIFIED);
user.setVerificationCode(RandomStringUtils.randomAlphanumeric(16));
userRepository.save(user);
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
try {
String verifyLink = Utilities.hostUrl() + "/users/" + user.getVerificationCode() + "/verify";
mailSender.send(user.getEmail(), Utilities.getMessage("verifySubject"), Utilities.getMessage("verifyEmail", verifyLink));
logger.info("Verification mail to " + user.getEmail() + " queued.");
} catch (MessagingException e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
}
});
}
}
I think you should go through Spring Reference about IoC container.
WebSecurityConfig class requires UserDetailsService, which is implemented by UserServiceImpl. Also, UserServiceImpl requires PasswordEncoder which is provided by WebSecurityConfig. This causes a circular reference. Removing constructor injection should be enough to resolve your problem.
Side note: Try not to use constructor injection. Spring is clever when it comes to DI, but if you use constructor injection you are forcing spring to use your way. This can also cause circular reference errors.
I recommend you to at least skim this article: https://steveschols.wordpress.com/2012/06/05/i-was-wrong-constructor-vs-setter-injection/

Categories