I am trying to create the Bean of FindByIndexNameSessionRepository. I need to get all user sessions using it but I am getting the bean error even I already defined it. I am using the Spring Boot Starter 1.5.7
Error: Field sessionRepository required a bean of type 'org.springframework.session.FindByIndexNameSessionRepository' that could not be found.
Consider defining a bean of type
'org.springframework.session.FindByIndexNameSessionRepository' in your
configuration.
I am trying to create bean and using it in my configuration, something like that:
import com.x.security.SpringSessionBackedSessionRegistry;
#Bean
SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry<ExpiringSession>(
this.sessionRepository);
}
#Autowired
private FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
My configuration is below
http<...>
.maximumSessions(2)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(false)
.<other settings>
My SpringSessionBackedSessionRegistry class is as follow:
public class SpringSessionBackedSessionRegistry<S extends ExpiringSession>
implements SessionRegistry {
private final FindByIndexNameSessionRepository<S> sessionRepository;
public SpringSessionBackedSessionRegistry(
FindByIndexNameSessionRepository<S> sessionRepository) {
Assert.notNull(sessionRepository, "sessionRepository cannot be null");
this.sessionRepository = sessionRepository;
}
#Override
public List<Object> getAllPrincipals() {
throw new UnsupportedOperationException("SpringSessionBackedSessionRegistry does "
+ "not support retrieving all principals, since Spring Session provides "
+ "no way to obtain that information");
}
#Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session
.getAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR))) {
infos.add(new SpringSessionBackedSessionInformation<S>(session,
this.sessionRepository));
}
}
return infos;
}
#Override
public SessionInformation getSessionInformation(String sessionId) {
S session = this.sessionRepository.getSession(sessionId);
if (session != null) {
return new SpringSessionBackedSessionInformation<S>(session,
this.sessionRepository);
}
return null;
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
#Override
public void refreshLastRequest(String sessionId) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
#Override
public void registerNewSession(String sessionId, Object principal) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
#Override
public void removeSessionInformation(String sessionId) {
}
/**
* Derives a String name for the given principal.
*
* #param principal as provided by Spring Security
* #return name of the principal, or its {#code toString()} representation if no name
* could be derived
*/
protected String name(Object principal) {
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
if (principal instanceof Principal) {
return ((Principal) principal).getName();
}
return principal.toString();
}
}
My pom snippet is as follows
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
</dependencies>
Any help is much appreciated.
Assuming you've got Spring Session configured properly (with Spring Boot 1.5.x that would be by setting spring.session.store-type configuration property to redis, or explicitly by using #EnableRedisHttpSession), you should be able to use FindByIndexNameSessionRepository<? extends ExpiringSession>. For example:
#Autowired
FindByIndexNameSessionRepository<? extends ExpiringSession> sessionRepository;
Related
I am new with Unit testing and Mockito. I'm trying to write tests for my Dao class:
#Repository
#NoArgsConstructor
public class UserDaoImpl implements UserDao {
private NamedParameterJdbcTemplate template;
#Value("${users.find.by_id}")
private String findByIdQuery;
private RowMapper<User> rowMapper = (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
user.setEmail(rs.getString("email"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
return user;
};
public UserDaoImpl(NamedParameterJdbcTemplate template) {
super();
this.template = template;
}
#Override
public Optional<User> findById(int id) {
SqlParameterSource param = new MapSqlParameterSource("id", id);
User user = null;
try {
user = template.queryForObject(findByIdQuery, param, BeanPropertyRowMapper.newInstance(User.class));
} catch (DataAccessException ex) {
ex.printStackTrace();
}
return Optional.ofNullable(user);
}
}
In my simple test I simply added #Mock anotation for my NamedParameterJdbcTemplate, and trying to put it into the UserDaoImpl:
public class UserDaoTest {
#Mock
public NamedParameterJdbcTemplate template;
#InjectMocks
public UserDao userDao;
#Test
public void findByIdTest() {
template = new NamedParameterJdbcTemplate(new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:db/schema.sql")
.addScript("classpath:db/test-data.sql")
.build());
userDao = new UserDaoImpl();
ReflectionTestUtils.setField(userDao, "template", template);
Mockito.when(userDao.findById(1).get().getEmail()).thenReturn("Keanu#gmail.com");
User user = userDao.findById(1).get();
assertNotNull(user);
assertEquals("Keanu#gmail.com", user.getEmail());
}
}
Each time I run the test, I get java.lang.NullPointerException for the field template. Cant find what is the correct way of implementing the test.
Here is my pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
You have multiple problems with your code:
You are using #Value directly in a property and you will have a hard time setting up your test for the class with this.
You are missing the enablement of Mockito annotations in your test.
You are instantiating UserDaoImpl in your test method when you should be relying on the instance created by Mockito.
You are also creating a NamedParameterJdbcTemplate and then using ReflectionTestUtils to wire it to the UserDaoImpl object.
And you are mocking the wrong object. You need to mock the calls to template, not userDao.
To tackle the first one you need to change UserDaoImpl as follows:
#Repository
#NoArgsConstructor
public class UserDaoImpl implements UserDao {
private NamedParameterJdbcTemplate template;
private String findByIdQuery;
private RowMapper<User> rowMapper = (rs, rowNum) -> {
User user = new User();
user.setId(rs.getInt("id"));
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
user.setEmail(rs.getString("email"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
return user;
};
public UserDaoImpl(NamedParameterJdbcTemplate template, #Value("${users.find.by_id}") String findByIdQuery) {
super();
this.template = template;
this.findByIdQuery = findByIdQuery;
}
#Override
public Optional<User> findById(int id) {
SqlParameterSource param = new MapSqlParameterSource("id", id);
User user = null;
try {
user = template.queryForObject(findByIdQuery, param, BeanPropertyRowMapper.newInstance(User.class));
} catch (DataAccessException ex) {
ex.printStackTrace();
}
return Optional.ofNullable(user);
}
}
To tackle 2., 3., 4. and 5. you need to enable Mockito annotations programmatically and remove userDao = new UserDaoImpl(); line and also the template variable from the method test as follows:
#RunWith(MockitoJUnitRunner.class)
public class UserDaoTest {
#Mock
public NamedParameterJdbcTemplate template;
public UserDao userDao;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
userDao = new UserDaoImpl(template, "query-string");
}
#Test
public void findByIdTest() {
// Arrange
User user = new User();
user.setId(rs.getInt("id"));
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
user.setEmail(rs.getString("Keanu#gmail.com"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
Mockito.when(template.queryForObject(anyString(), any(SqlParameterSource.class), any(RowMapper.class))).thenReturn(user);
template.queryForObject(findByIdQuery, param, BeanPropertyRowMapper.newInstance(User.class));
// Act
User user = userDao.findById(1).get();
// Assert
assertNotNull(user);
assertEquals("Keanu#gmail.com", user.getEmail());
}
}
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
It solved!!!
I knew why spring throws exception at autowiring.
This problem is caused by my validation configuration.
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "io.zerogone")
public class WebConfiguration extends WebMvcConfigurerAdapter {
//etc
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor(); // this is
}
}
MethodValidationPostProcessor creates proxy beans that attached #Validated.
This proxy beans based jdk-based proxy.
So when Spring is autowiring, It may be confused because it lost SearchService implementation information.
Read this.
https://github.com/spring-projects/spring-boot/issues/17000
and my project's Spring boot version works well because it based on CGLib proxy.
This problem solved after MethodValidationPostProcessor
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
postProcessor.setProxyTargetClass(true); // it configures based on CGLib proxy
return postProcessor;
}
Therefore, I can autowiring without #Qualifier for generic implementation.
Things to read further.
What is the difference between JDK dynamic proxy and CGLib?
https://www.baeldung.com/spring-autowire-generics
I found your error very interesting and created the same project that you have. It seems that for me the spring boot app can start without any errors even with #Validated annotation
So something else is going on in your project
Question regarding how to have Spring Webflux Websocket as annotation please.
I am working on a Springboot Webflux Websocket project where it handles BOTH restful api AND a websocket.
To emphasize, it is a Spring Webflux project alone. Not a Springboot starter websocket, not a Springboot rsocket, not a Springboot pub sub.
The project is as follow:
#SpringBootApplication
public class EchoApplication {
public static void main(String[] args) {
SpringApplication.run(EchoApplication.class, args);
}
}
#RestController
public class EchoController {
#GetMapping(value = "/getEcho")
public Mono<String> getEcho() {
return Mono.just("echo");
}
}
public class EchoHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession session) {
return session.send( session.receive().map(msg -> "RECEIVED ON SERVER :: " + msg.getPayloadAsText()).map(session::textMessage));
}
}
#Configuration
public class EchoConfiguration {
#Bean
public EchoHandler echoHandler() {
return new EchoHandler();
}
#Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/echo", echoHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return mapping;
}
#Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter(webSocketService());
}
#Bean
public WebSocketService webSocketService() {
return new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
}
}
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
As you can see, in this project, there is the "traditional" annotation #GetMapping(value = "/getEcho"). Many Spring projects uses this style where it is exposed as annotation (event rscocket, pub/sub, etc...)
How to have:
#Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/echo", echoHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
as annotation, some kind of #WebsocketMapping("/echo") which will help present an unified and cleaner way to see routes?
Thank you
From Spring Team, this is not currently supported, neither have plan to support route based web socket for Webflux.
I'm feeling stupid to ask this, but I can't understand where I'm wrong with my code.
The context is :
a Spring Boot application (1.5.7) with an embedded Jetty server and a
controller to expose some endpoints
a unique #Configuration class, where some of my beans are defined (Singleton and Prototype scopes)
a #Service that uses some beans defined in my #Configuration class
The problem is:
a NoSuchBeanDefinitionException for one of my #Configuration bean.
Now the details:
My SpringBootApplication :
#SpringBootApplication
public class HbbTVApplication {
public static void main(String[] args) {
SpringApplication.run(HbbTVApplication.class, args);
}
}
My #Configuration class:
#Configuration
#Profile(value = { "dev", "int", "pre", "pro" })
public class StandaloneFrontalConfig extends WebMvcConfigurerAdapter {
#Value("${kafka.bootstrap-servers}")
private String bootstrapServers;
#Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return props;
}
#Bean
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
#Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/standalone/");
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*");
}
};
}
#Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
#Bean
public Security securityManager() {
return new Security();
}
#Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KngAflow getTechnicalCookie() {
return new KngAflow();
}
#Bean
public EmbeddedServletContainerCustomizer customizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof JettyEmbeddedServletContainerFactory) {
customizeJetty((JettyEmbeddedServletContainerFactory) container);
}
}
private void customizeJetty(JettyEmbeddedServletContainerFactory jetty) {
jetty.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
for (Connector connector : server.getConnectors()) {
if (connector instanceof ServerConnector) {
HttpConnectionFactory connectionFactory = ((ServerConnector) connector)
.getConnectionFactory(HttpConnectionFactory.class);
connectionFactory.getHttpConfiguration().setCookieCompliance(CookieCompliance.RFC2965);
}
}
}
});
}
};
}
}
My #Service:
#Service
public class CookieService implements services.CookieService, InitializingBean {
/**
* Serializable
*/
private static final long serialVersionUID = -1997257884335775587L;
#Autowired
ApplicationContext app;
#Override
public Cookie createTechnicalCookie() {
return new Cookie(app.getBean(KngAflow.class), null);
}
#Override
public void afterPropertiesSet() throws Exception {
if (app != null) {
for (String bean : app.getBeanDefinitionNames()) {
System.out.println("Bean: " + bean);
}
}
}
}
And the "non defined" bean:
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties({ "security", "maxAge", "domain", "updated" })
public class KngAflow implements Serializable, InitializingBean {
#JsonProperty(value = "did")
private String did;
#JsonProperty(value = "checksum")
private String checksum;
#Autowired
private Security security;
private Integer maxAge;
private String domain;
private boolean updated = false;
public KngAflow() {
domain = ".mydomain.com";
}
#Override
public void afterPropertiesSet() throws Exception {
did = UUID.randomUUID().toString();
maxAge = 365 * 24 * 60 * 60;
checksum = security.encrypt(did + security.md5(did));
}
}
NB: Classes are not complete, and there are more classes in my project. I only put what I saw as relevant information.
If something else is needed, just ask me please.
By the way, all the endpoints are defined into a unique #Controller class, and all the endpoints are working except those needing the getTechCookie #Bean.
So, my problem occurs in runtime execution. When I start my Spring Boot app, Jetty is started and listening on the configured port.
Nevertheless, if you look at the CookieService #Service, I'm listing all the bean names defined in the autowired context and my getTechnicalCookie (a.k.a KngAflow) #Bean is missing. I can't understand why.
Of course, when I invoke my #controller to execute my #Service code, the NoSuchBeanDefinitionException is thrown executing the line app.getBean(KngAflow.class).
I tried to use a bean name instead of bean type, no change.
For testing purpose (as it doesn't make sense from a logical point of view), I defined my bean getTechCookie #Bean as a Singleton scoped bean, and the name is still missing from the ApplicationContext.
And the last but not least thing is: Everything works fine with Eclipse!
I mean, all my devs are done using Eclipse IDE. My Spring Boot app is built with Maven and executing it inside Eclipse works correctly (and my getTechCookie Bean is defined and listed).
When I package my app using the Maven Spring Boot plugin and execute it using java -jar, my getTechCookie (KngAflow.class) bean is missing. Nevertheless, this class is present inside the jar.
Spring parameters to launch the spring boot app are spring default values (port 8080, no SSL, ...) and the active.profiles are always between dev, int, pre or pro (those defined in my #Configuration class)
What am I doing wrong?
Thanks!
If it helps, I add my POM definition:
<?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>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.mydomain.bigdata</groupId>
<artifactId>mybigapp</artifactId>
<version>1.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
<include>application.yml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
EDIT: I changed my #Service class to "force" spring to accept my class as a prototype bean, and it works. It's very ugly but it works. But if someone could help me to find what's wrong, I don't like this workaround:
#Override
public void afterPropertiesSet() throws Exception {
if (!context.containsBeanDefinition(KngAflow.class.getName()))
context.registerBeanDefinition(KngAflow.class.getName(),
BeanDefinitionBuilder.genericBeanDefinition(KngAflow.class).setScope("prototype").getBeanDefinition());
}
I made a following simple application to reproduce issue.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
run(Application.class, args);
}
}
#Configuration
#Profile("dev")
public class BeanConfiguration {
#Bean
#Scope(scopeName = SCOPE_PROTOTYPE)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
public class PrototypeBean {}
#Service
#Slf4j
public class SingletonBean implements InitializingBean {
#Autowired
private ApplicationContext context;
public PrototypeBean getPrototypeBean() {
return context.getBean(PrototypeBean.class);
}
#Override
public void afterPropertiesSet() throws Exception {
for (String name : context.getBeanDefinitionNames()) {
Class<?> c = context.getBean(name).getClass();
log.debug("===> Name: {}, Type = {}", name, c.getTypeName());
}
}
}
#RestController
#RequestMapping("/bean")
public class BeanRestController {
#Autowired
private SingletonBean singletonBean;
#GetMapping("/name")
public String getName() {
return singletonBean.getPrototypeBean().getClass().getName();
}
}
When I execute application with -Dspring.profiles.active=dev setting
Then I see in the log without no issue and REST endpoint gives back response properly:
===> Name: prototypeBean, Type = PrototypeBean
But if I execute application without profile setting
Then I see error in the log and REST endpoint raise exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'PrototypeBean' available
I am trying to add #NotNull constraint into my Person object but I still can #POST a new Person with a null email. I am using Spring boot rest with MongoDB.
Entity class:
import javax.validation.constraints.NotNull;
public class Person {
#Id
private String id;
private String username;
private String password;
#NotNull // <-- Not working
private String email;
// getters & setters
}
Repository class:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
}
Application class:
#SpringBootApplication
public class TalentPoolApplication {
public static void main(String[] args) {
SpringApplication.run(TalentPoolApplication.class, args);
}
}
pom.xml
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
...
When I #POST a new object via Postman like:
{
"username": "deadpool",
"email": null
}
I still get STATUS 201 created with this payload:
{
"username": "deadpool",
"password": null,
"email": null
....
....
}
I had the same problem, but just enabling validation didn't work for me, this did work with both JPA and MongoDb to save anyone else spending ages on this. Not only does this get validation working but I get a nice restful 400 error rather than the default 500.
Had to add this to my build.gradle dependencies
compile('org.hibernate:hibernate-validator:4.2.0.Final')
and this config class
#Configuration
public class CustomRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("afterCreate", validator());
validatingListener.addValidator("beforeCreate", validator());
validatingListener.addValidator("afterSave", validator());
validatingListener.addValidator("beforeSave", validator());
}
}
i found it better to make my own version of #NotNull annotation which validates empty string as well.
#Documented
#Constraint(validatedBy = NotEmptyValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotEmpty {
String message() default "{validator.notEmpty}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class NotEmptyValidator implements ConstraintValidator<NotEmpty, Object> {
#Override
public void initialize(NotEmpty notEmpty) { }
#Override
public boolean isValid(Object obj, ConstraintValidatorContext cxt) {
return obj != null && !obj.toString().trim().equals("");
}
}
You can either use the following code for validating
#Configuration
#Import(value = MongoAutoConfiguration.class)
public class DatabaseConfiguration extends AbstractMongoConfiguration
{
#Resource
private Mongo mongo;
#Resource
private MongoProperties mongoProperties;
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Override
protected String getDatabaseName() {
return mongoProperties.getDatabase();
}
#Override
public Mongo mongo() throws Exception {
return mongo;
}
}
Normally, the #RestRepository will resolve into a controller than handles validation by itself, except if you Override the default behavior or it by including some #HandleBeforeSave, #HandleBeforeCreate, ... into your code.
A solution is to remove the #HandleBeforeSave, #HandleBeforeCreate, ...
and then spring will handle the validation again.
Or if you want to keep them, you can provide a handler for any object validation like this:
#Component
#RepositoryEventHandler
public class EntityRepositoryEventHandler {
#Autowired
private Validator validator;
#HandleBeforeSave
#HandleBeforeCreate
public void validate(Object o) {
Set<ConstraintViolation<Object>> violations = this.validator.validate(o);
if (!violations.isEmpty()) {
ConstraintViolation<Object> violation = violations.iterator().next();
// do whatever your want here as you got a constraint violation !
throw new RuntimeException();
}
}
}