I tried to implement the polymorphism in the service layer.
and injecting component was working in the controller.
But It doesn't worked trying to use validation api with @Valid @Validated
@Validated
public interface SearchService<K, V> {
V search(@NotNull @Valid K key);
}
@Service
public class UserSearchService implements SearchService<Email, UserDto> {
private final UserDao userDao;
private final Converter<User, UserDto> converter;
public UserSearchService(UserDao userDao, Converter<User, UserDto> converter) {
this.userDao = userDao;
this.converter = converter;
}
@Override
public UserDto search(Email email) {
try {
User entity = userDao.findByEmail(email.get());
return converter.convert(entity);
} catch (NoResultException noResultException) {
throw new NotExistDataException("user not found", email.get());
}
}
}
@RestController
@Validated
public class UserSearchController {
private final SearchService<Email, UserDto> searchService;
public UserSearchController(SearchService<Email, UserDto> searchService) {
this.searchService = searchService;
}
@GetMapping("api/user")
public UserDto handleSearchingUserByEmail(@RequestParam @Valid Email email) {
return searchService.search(email);
}
}
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userSearchController' defined in file [C:\Users\younggon\Desktop\studylog\target\classes\io\zerogone\controller\api\UserSearchController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'io.zerogone.service.search.SearchService<?, ?>' available: expected single matching bean but found 2: blogSearchService,userSearchService
and spring is matching this service too
@Service
public class BlogSearchService implements SearchService<BlogName, BlogDto> {
private final BlogDao blogDao;
private final Converter<Blog, BlogDto> converter;
public BlogSearchService(BlogDao blogDao, Converter<Blog, BlogDto> converter) {
this.blogDao = blogDao;
this.converter = converter;
}
@Override
public BlogDto search(BlogName blogName) {
try {
Blog entity = blogDao.findByName(blogName.get());
return converter.convert(entity);
} catch (NoResultException noResultException) {
throw new NotExistDataException("blog not found", blogName.get());
}
}
}
Actually, @Qualifier can be used to solve it. but I wonder why. help me :(
update.
I tested autowiring works well without @Qualifier and @Validated
public interface SearchService<K, V> {
V search(K key);
}
@RestController
@Validated
public class UserSearchController {
private final SearchService<Email, UserDto> searchService;
public UserSearchController(SearchService<Email, UserDto> searchService) {
this.searchService = searchService;
}
@GetMapping("api/user")
public UserDto handleSearchingUserByEmail(@RequestParam @Valid Email email) {
return searchService.search(email);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebConfiguration.class, DatabaseConfiguration.class}, loader = AnnotationConfigWebContextLoader.class)
@WebAppConfiguration
public class UserSearchControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void handleSearchingUserByEmail() throws Exception {
mockMvc.perform(get("/api/user")
.param("email", "dudrhs571@gmail.com"))
.andExpect(status().isOk())
.andDo(print());
}
}
result is here.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/user
Parameters = {email=[dudrhs571@gmail.com]}
Headers = {}
Handler:
Type = io.zerogone.controller.api.UserSearchController
Method = public io.zerogone.model.dto.UserDto io.zerogone.controller.api.UserSearchController.handleSearchingUserByEmail(io.zerogone.model.Email)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"id":1,"name":"zeroGone","nickName":"zeroGone","email":"dudrhs571@gmail.com","imageUrl":"url","blogs":[{"id":1,"name":"studylog","introduce":"web platform for team blog","imageUrl":"/img/blog-default.png","members":null,"invitationKey":null}]}
Forwarded URL = null
Redirected URL = null
Cookies = []
But it deosn't works after adding @Validated to SearchService interface
@Validated
public interface SearchService<K, V> {
V search(@NotNull @Valid K key);
}
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userSearchController' defined in file [C:\Users\younggon\Desktop\studylog\target\classes\io\zerogone\controller\api\UserSearchController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'io.zerogone.service.search.SearchService<?, ?>' available: expected single matching bean but found 2: blogSearchService,userSearchService
I guess Spring is autowiring generic type for UserSearchService, BlogSearchService
Actually it works
but after adding @Validated it doesn't works
and testing directly get UserSearchService.class doesnt't works too.
@Test
public void getUserSearchService() {
Assert.assertEquals(UserSearchService.class, webApplicationContext.getBean(UserSearchService.class).getClass());
}
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.zerogone.service.search.UserSearchService' available
- I wonder Autowiring for SearchService type is worked by distinguish generic type
- And why after adding @Validated it doesn't work?
update2.
I know it works well my code in spring boot app
I hope my spring project works well
Here's my project structure summary
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.zerogone</groupId>
<artifactId>studylog</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.version>4.3.30.RELEASE</spring.version>
<apache.commons.version>1.4</apache.commons.version>
<jackson.version>2.12.2</jackson.version>
</properties>
<dependencies>
<!--mvc start-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${apache.commons.version}</version>
</dependency>
<!--mvc end-->
<!--test start-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!--test end-->
<!--database start-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${apache.commons.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.29.Final</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<!--database end-->
<!--log start-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--log end-->
<!--validation start-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<!--validation start-->
</dependencies>
</project>
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "io.zerogone")
public class WebConfiguration extends WebMvcConfigurerAdapter {
private static final int TEN_MEGA_BYTE = 10 * 1024 * 1024;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/img/**").addResourceLocations("/img/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(TEN_MEGA_BYTE);
multipartResolver.setMaxUploadSizePerFile(TEN_MEGA_BYTE);
return multipartResolver;
}
@Bean
public ConversionServiceFactoryBean conversionServiceFactory(Set<Converter<?, ?>> converters) {
ConversionServiceFactoryBean conversionServiceFactory = new ConversionServiceFactoryBean();
conversionServiceFactory.setConverters(converters);
return conversionServiceFactory;
}
@Bean
public ConversionService conversionService(ConversionServiceFactoryBean factory) {
return factory.getObject();
}
@Bean
public ConfigurableWebBindingInitializer webBindingInitializer(ConversionService conversionService) {
ConfigurableWebBindingInitializer configurableWebBindingInitializer = new ConfigurableWebBindingInitializer();
configurableWebBindingInitializer.setConversionService(conversionService);
return configurableWebBindingInitializer;
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
public class ApplicationInitializer implements WebApplicationInitializer {
private static final String ROOT_PACKAGE = "io.zerogone";
private static final String DISPATCHER_NAME = "dispatcher";
private static final int DISPATCHER_LOAD_NUMBER = 1;
private static final String DISPATCHER_MAPPING_URL = "/";
@Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.scan(ROOT_PACKAGE);
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic appServlet = servletContext.addServlet(DISPATCHER_NAME, new DispatcherServlet(new GenericWebApplicationContext()));
appServlet.setLoadOnStartup(DISPATCHER_LOAD_NUMBER);
appServlet.addMapping(DISPATCHER_MAPPING_URL);
}
}
and Tell me necessary information for help
Thanks for any help