I need such a usage:
For each request I want to inject userId into DemoController But because of being a final class without empty constructor I can not inject it. What is the best practice in such cases? A service with request scope is fine?
#Configuration
public class CityFactory{
#Bean(name = {"currentUserId")
#Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.TARGET_CLASS)
#Autowired
public Integer getUserId(HttpServletRequest request) {
return UserUtil.getCurrentUserId(request.getServerName());
}
}
#RequestMapping("/demo")
#Controller
public class DemoController {
#Autowired
Ingeter userId;
#RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
public ModelAndView helloWorld(#PathVariable("name") String name, Model model) {
Map<String, Object> myModel = new HashMap<String, Object>();
model.addAttribute("user", userId);
return new ModelAndView("v3/test", "m", model);
}
}
Your best bet is to create an explicit class called UserId, which in turn contains an integer. Not only will this play nicer with CGLIB's proxying, it also clarifies your design.
You can use Supplier or Provider
#Configuration
public class CityFactory{
#Bean
#Autowired
public Supplier<Integer> getUserId(HttpServletRequest request) {
return () -> UserUtil.getCurrentUserId(request.getServerName());
}
}
#RequestMapping("/demo")
#Controller
public class DemoController {
#Autowired
Supplier<Ingeter> getUserId;
Related
I can't autowire Mapper from mapstruct in Tests. In Test with SpringRunner.class its ok, but when I try with MockitoJunitRunner its not possible.
#Mapper(componentModel = "spring", uses = {})
public interface UserMapper {
UserMapper MAPPER = Mappers.getMapper(UserMapper.class);
User mapToUser(UserDto userDto);
UserDto mapToUserDto(User user);
List<UserDto> mapToUserDtoList(List<User> userList);
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class nowyTest {
#Spy
private UserMapper userMapper;
private User createUser() {
return User.builder()
.firstName("Steve")
.lastName("Jobs")
.login("SteveJobs")
.password("password")
.role(UserRole.ROLE_ADMIN)
.build();
}
#Test
public void testMapper() {
User user = createUser();
UserDto userDto = userMapper.mapToUserDto(user);
System.out.println(userDto);
Assert.assertEquals(userDto.getFirstName(), "Steve");
}
}
It returns NPE :(
Have you tried to include the annotation #Import(yourClass)? in this case:
#RunWith(SpringRunner.class)
#SpringBootTest
#Import(UserMapper.class)
public class nowyTest {
...
You can check this tutorial for a bit more information on how to start beans in SpringBoot tests.
I have an ApplicationProperties that i use it in my service. When i mock the rest template in my test to test that service it results in java.lang.NullPointerException, when i replace applicationProperties.getBebusiness().getEndPointProvider() with the url, the test passes
#Configuration
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties { ...
}
#Service
public class ProviderServiceImpl implements ProviderService {
private final ApplicationProperties applicationProperties;
private final RestTemplate restTemplate;
public ProviderServiceImpl(ApplicationProperties applicationProperties, RestTemplate restTemplater) {
this.applicationProperties = applicationProperties;
this.restTemplate = restTemplate;
}
#Override
public List <ProviderDTO> getAllProviderFromBebusiness() {
final List <ProviderDTO> result = new ArrayList<>();
final ResponseEntity <String> responseEntity = restTemplate.getForEntity(
applicationProperties.getBebusiness().getEndPointProvider() + "&page=0&size=" + Integer.MAX_VALUE, String.class);
if (responseEntity.getStatusCodeValue() == 200) {}
return result;
}
}
public class ProviderServiceTest {
#Autowired
private ApplicationProperties applicationProperties;
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#InjectMocks
private ProviderServiceImpl providerService;
#Test
public void givenMockingIsDoneByMockito_whenGetIsCalled_shouldReturnMockedObject() {
String provider = providerInit.buildProviderDTOWithIdFromBebusiness();
ResponseEntity <String> responseEntity = new ResponseEntity <String> (provider, HttpStatus.OK);
when(restTemplate.getForEntity(anyString(), ArgumentMatchers.any(Class.class)))
.thenReturn(responseEntity);
List<ProviderDTO> result = providerService.getAllProviderFromBebusiness();
assertEquals(200, responseEntity.getStatusCodeValue());
}
}
Seems your spring beans are not instantiated. You have to annotate your test class to tell spring it has to initialize its context. Try use #SpringBootTest annotation
You can either use #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) annotation or if you don't want to run Spring Boot context then just mock your configuration class as following:
#BeforeEach // #Before if you're using JUnit4
void setUp() {
Bebusiness bebusiness = mock(Bebusiness.class);
when(applicationProperties.getBebusiness()).thenReturn(bebusiness);
when(bebusiness.getEndPointProvider()).thenReturn("http://localhost:8080");
}
You can use
#TestPropertySource(locations = "/other-location.properties")
Or
#TestPropertySource(properties = "spring.datasource.max-active=30,
spring.datasource.initial-size=5")
This is a exemple : https://www.logicbig.com/tutorials/spring-framework/spring-core/test-property-source-with-inlined-properties.html
Main Service class
#Service
public class MainService {
#Autowired
HouseRepository repository;
#Autowired
HouseDtoConverter converter;
public House addNewHouse(HouseDTO houseDTO) {
House h = converter.convert(houseDTO);
repository.save(h);
return h;``
}
public Iterable<House> getAllHouses() {
return repository.findAll();
}
}
Main Controller class
#RestController // This means that this class is a Controller
#RequestMapping("/demo")
public class MainController {
#Autowired
MainService service;
#RequestMapping(value = "/add", method = RequestMethod.POST) // Map ONLY GET Requests
#ResponseBody
public House addNewHouse (#RequestBody HouseDTO houseDTO) {
return service.addNewHouse(houseDTO);
}
#RequestMapping(value="/all", method = RequestMethod.GET)
#ResponseBody
public Iterable<House> getAllHouses() {
// This returns a JSON or XML with the houses
return service.getAllHouses();
}
And the stack trace:
Description:
Field repository in vikings.service.MainService required a bean of type 'vikings.repository.HouseRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'vikings.repository.HouseRepository' in your configuration.
That's my SpringBootApplication
#Component
#SpringBootApplication
public class SpringBoot11Application {
public static void main(String[] args) {
SpringApplication.run(SpringBoot11Application.class, args);
}
}
I'm building a microservice architecture with Spring Framework, Feign and Eureka.
Shop works as an API Gateway, realised with Zuul (no database)
Each microservice has its own h2 database.
The CustomerService can call cart mappings with Feign
Example: I have a "Shop" and want to create a customer with a cart in one method in the service itself.
How can I call methods in the ShopService, to manage the other Services without having relationships to the other services? Please let me know if you need code examples.
CustomerController.java:
#ComponentScan
#RestController
public class CustomerController {
final Customer2CartConnectorRequester customer2CartConnectorRequester;
final Customer2OrderConnectorRequester customer2OrderConnectorRequester;
#Autowired
private CustomerJpaRepository customerJpaRepository;
#Autowired
public CustomerController(Customer2CartConnectorRequester customer2CartConnectorRequester,
Customer2OrderConnectorRequester customer2OrderConnectorRequester) {
this.customer2CartConnectorRequester = customer2CartConnectorRequester;
this.customer2OrderConnectorRequester = customer2OrderConnectorRequester;
}
#GetMapping("/list")
#ResponseBody
public List<CustomerEntity> getCustomers() {
return customerJpaRepository.findAll();
}
#RequestMapping("/{customerId}")
#ResponseBody
public Optional<CustomerEntity> getCustomer(#PathVariable("customerId") int customerId) {
return customerJpaRepository.findById(customerId);
}
// Client to Server
// POST über z.B. Postman Client
#PostMapping("/customer")
public CustomerEntity addCustomer(#RequestBody CustomerEntity customerEntity) {
customerJpaRepository.save(customerEntity);
return customerEntity;
}
#DeleteMapping("customer/{customerId}")
public String deleteCustomer(#PathVariable int customerId) {
CustomerEntity a = customerJpaRepository.getOne(customerId);
customerJpaRepository.delete(a);
return "deleted";
}
#PutMapping("/customer")
public CustomerEntity updateCustomer(#RequestBody CustomerEntity customerEntity) {
customerJpaRepository.save(customerEntity);
return customerEntity;
}
/// CONNECTOR REQUESTER ///
#GetMapping("/cart")
public List<?> getCart() {
return customer2CartConnectorRequester.getCart();
}
#GetMapping("/orders")
public List<?> getOrders() {
return customer2OrderConnectorRequester.getOrders();
}
}
Update:
I tried using a POST-Method to send data via Postman Client like this, but it seems like there is still something wrong:
Shop2CustomerConnectorRequester:
#FeignClient("customermicroservice")
public interface Shop2CustomerConnectorRequester {
#RequestMapping(value = "/list", method = RequestMethod.POST)
public ResponseEntity<String> createCustomer(Map<String, ?> queryMap);
}
ShopController:
#ComponentScan
#RestController
public class ShopController {
final Shop2CustomerConnectorRequester shop2CustomerConnectorRequester;
#Autowired
private ShopJpaRepository shopJpaRepository;
#Autowired
public ShopController(Shop2CustomerConnectorRequester shop2CustomerConnectorRequester) {
this.shop2CustomerConnectorRequester = shop2CustomerConnectorRequester;
}
#GetMapping("/customer")
public ResponseEntity<String> createCustomer(Map<String, ?> queryMap) {
return shop2CustomerConnectorRequester.createCustomer(queryMap);
}
}
I am trying to put validation to a Spring Boot project. So I put #NotNull annotation to Entity fields. In controller I check it like this:
#RequestMapping(value="", method = RequestMethod.POST)
public DataResponse add(#RequestBody #Valid Status status, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return new DataResponse(false, bindingResult.toString());
}
statusService.add(status);
return new DataResponse(true, "");
}
This works. But when I make it with input List<Status> statuses, it doesn't work.
#RequestMapping(value="/bulk", method = RequestMethod.POST)
public List<DataResponse> bulkAdd(#RequestBody #Valid List<Status> statuses, BindingResult bindingResult) {
// some code here
}
Basically, what I want is to apply validation check like in the add method to each Status object in the requestbody list. So, the sender will now which objects have fault and which has not.
How can I do this in a simple, fast way?
My immediate suggestion is to wrap the List in another POJO bean. And use that as the request body parameter.
In your example.
#RequestMapping(value="/bulk", method = RequestMethod.POST)
public List<DataResponse> bulkAdd(#RequestBody #Valid StatusList statusList, BindingResult bindingResult) {
// some code here
}
and StatusList.java will be
#Valid
private List<Status> statuses;
//Getter //Setter //Constructors
I did not try it though.
Update:
The accepted answer in this SO link gives a good explanation why bean validation are not supported on Lists.
Just mark controller with #Validated annotation.
It will throw ConstraintViolationException, so probably you will want to map it to 400: BAD_REQUEST:
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice(annotations = Validated.class)
public class ValidatedExceptionHandler {
#ExceptionHandler
public ResponseEntity<Object> handle(ConstraintViolationException exception) {
List<String> errors = exception.getConstraintViolations()
.stream()
.map(this::toString)
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponseBody(exception.getLocalizedMessage(), errors),
HttpStatus.BAD_REQUEST);
}
private String toString(ConstraintViolation<?> violation) {
return Formatter.format("{} {}: {}",
violation.getRootBeanClass().getName(),
violation.getPropertyPath(),
violation.getMessage());
}
public static class ErrorResponseBody {
private String message;
private List<String> errors;
}
}
#RestController
#Validated
#RequestMapping("/products")
public class ProductController {
#PostMapping
#Validated(MyGroup.class)
public ResponseEntity<List<Product>> createProducts(
#RequestBody List<#Valid Product> products
) throws Exception {
....
}
}
with using Kotlin and Spring Boot Validator
#RestController
#Validated
class ProductController {
#PostMapping("/bulk")
fun bulkAdd(
#Valid
#RequestBody statuses: List<Status>,
): ResponseEntity<DataResponse>> {...}
}
data class Status(
#field:NotNull
val status: String
)