Spring Boot can't access REST Controller - java

I have a simple problem - SpringBootApplication doesn't see my controller - what's more weird - only one of three.
I have UserController, WalletController and DashboardController - this one is not visible for my application.
What I have already done is:
Every package with controller is under the main package, where my SpringBootApplication.class is,
I tried annotate main SpringBootApplication.class with #ComponentScan both with basePackages and basePackageClasses,
There is no other beans - which should be annotated #Component, I removed them and moved methods to my DashboardService.class
This is my controller, which is not visible:
DashboardController
And this is my package structure(seems to be right): Package Structure
Thank You for help!
EDIT:
It might be important, that I use the third-party api to get the data I need
In that methods I use url:
private String getNbpJson(String url) {
return new RestTemplate().getForObject(url, String.class);
}
private CurrentRateDTO getCurrentExchangeRate(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
String code = node.get("code").toString();
String date = node.get("rates").get(0).get("effectiveDate").toString();
double bid = node.get("rates").get(0).get("bid").asDouble();
double ask = node.get("rates").get(0).get("ask").asDouble();
return new CurrentRateDTO(code, date, bid, ask);
}
And then in ExchangeService this is my url
#Service
public class ExchangeRateService implements IExchangeRateService {
private static final Logger logger = LoggerFactory.getLogger(ExchangeRateService.class);
private String NBP_CURRENT_RATE_URL = "http://api.nbp.pl/api/exchangerates/rates/c/eur/2020-12-11/?format=json";
What's more... when I move methods from DashboardController to WalletController (which works)
Another thing that I have found out is that only methods, which make use of the third-party api don't work.
Basically, I retrieve data from the url above - I get the specific fields, create an objects with filled fields.
May it be a problem with retrieving data from the third-party and then implementing it in my app?
I have no more ideas for now...

Maybe your controller is registered but you type a slightly different url. Try this property logging.level.org.springframework.web.servlet.mvc.method.annotation: TRACE
and check on application startup if the controller is registered under some other url.
Try to add this to your controller
#RestController()
#RequestMapping("exchangerates")

Okay, I probably found out the problem... Before I have 3 controllers:
#RestController("/api")
public class UserController {}
#RestController("/wallets")
public class WalletController {}
#RestController("/exchangerates")
public class DashboardController {}
I changed the above to
#RestController("/api")
public class UserController {}
#RestController
public class WalletController {}
#RestController
public class DashboardController {}
So, basically I removed base ?endpoint? for each controller and now it works... It seems like basic endpoints in three RestControllers is too much and the third one is not available, but I don't know is it truth (I bet that it's not).
Why that happened then, can anybody explain that behaviour of controllers?
Thank You for explanantion.

Related

Spring MVC: One View - Multiply Controllers

For note, I'm using Thymeleaf & Spring 2.5.4. For example, I've three different "banner" entities that I need to show on one page. There are "mainBanner", "backgroundBanner" and "newsBanner". First of all, is it the right way to combine controllers in one (in the frame of banner entities)? Or is there exist any standard that says we must write controllers separately for each entity? But the main question is how to write #RequestingMapping correctly for the banner page? I have the banner page ("/admin/banners/") where should be three tables of those entities. As I understand I need to create BannerPageController with #RequestingMapping("/admin/banners/"), isn't it? Hoping for any help in solving
I've written controllers this way:
#MainBannerController.class
#Controller
#RequestMapping("admin/banners/main/")
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class MainBannerController {
...
#BackgroundBannerController.class
#Controller
#RequestMapping("admin/banners/background/")
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class MainBannerController {
...
#NewsBannerController.class
#Controller
#RequestMapping("admin/banners/news/")
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class MainBannerController {
...
Moreover, how to get 3 different models for one view?
#BannerController.class ???
#Controller
#RequestMapping("admin/banners/main/")
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class MainBannerController {
private final MainBannerService mainBannerService;
private final MainBannerService mainBannerService;
private final MainBannerService mainBannerService;
// How to get 3 different models for one view?
#GetMapping({"/", ""})
public ModelAndView allBanners() {
// new ModelAndView("/admin/banners/index", "mainBanners", mainBannerService.getAllMainBanners());
// new ModelAndView("/admin/banners/index", "backgroundBanners", backgroundBannerService.getAllBackgroundBanners());
// new ModelAndView("/admin/banners/index", "newsBanners", newsBannerService.getAllNewsBanners());
return null;
}
create your modelAndView
ModelAndView modelAndView = new ModelAndView("/admin/banners/index");
then add as many object as you want, under a different name each
modelAndView.addObject("mainBanners", mainBannerService.getAllMainBanners());
modelAndView.addObject("backgroundBanners",mainBannerService.getAllBackgroundBanners());
return modelAndView;
There is no any rule that you have to create different controllers for different URI but if you create different one that will be easy to understand where you mapped each of them.
If you are creating only one controller
Use #RestController and #RequestMapping("/your uri") for every different method.

Spring boot custom resolver for class variable

I'm trying to achieve something like this:
#Controller
public SomeController {
#CustomConfig("var.a")
private String varA;
#CustomConfig("var.b")
private String varB;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String get() {
return varA;
}
}
CustomConfig would be an #Interface class that accepts one value parameter. The reason why we are not using #Value is because this will not come from config file but from API (such as https://getconfig.com/get?key=var.a). So we are going to make HTTP request to inject it.
So far I've only manage to make something work if the varA and varB is inside get() method as parameter, by using below in a class that extends WebMvcConfigurerAdapter:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
CustomConfigResolver resolver = new CustomConfigResolver();
argumentResolvers.add(resolver);
}
And inside CustomComfigResolver.resolveArgument() we would do the HTTP query, but that's not really what we wanted, we need it to be injected as class variable.
Does anyone have experience in resolving it at class variable level?
Thank you
This could work if you use #Value instead of your own custom annotation. This uses the built in environment:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class TcpIpPropertySourceConfig implements InitializingBean {
#Autowired
private ConfigurableEnvironment env;
#Autowired
private RestTemplate rest;
public void afterPropertiesSet() {
// Call your api using Resttemplate
RemoteProperties props = //Rest Call here;
// Add your source to the environment.
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource("customSourceName", props)
}
}
What you are trying to achieve is difficult when you start to consider "unhappy" scenarios. Server down / not reachable. You need to account for all of that in the method above.
I would highly recommend to instead use Spring Cloud Config. Great guide on that is here: https://www.baeldung.com/spring-cloud-configuration
This provides:
- Reloading of your #Value() properties, so no custom annotation needed.
- A more stable server and great Spring integration out of the box.
Best of all, it is easy to apply Retries and Backoffs if the configuration server goes down (see https://stackoverflow.com/a/44203216/2082699). This will make sure your app doesn't just crash when the server is not available.

How to pass an argument to a Spring Boot REST Controller?

I am trying to pass an argument to my #RESTController Spring Boot class.
In the #POSTMapping method I want to use a method of a self defined Java class for processing the received body and returning a response.
The Spring application is launched in Application.java. The Controller-Object seems to get created implicitly.
I already tried adding a constructor to my RESTController class. But I couldn't find a way to call that constructor with an argument.
// Application.java
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
//ConnectorService.java
#RestController
public class ConnectorService {
private Solveable solver;
public ConnectorService() {}
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestBody Test test) {
return solver.solve(test);
}
}
Even though i could define a second constructor, i didn't find any way to call it with my Object.
Use #RequestParam annotation to pass an argument
You can pass parameter with #RequestParam annotation like this:
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestParam("paramName") String param, #RequestBody Test test) {
return solver.solve(test);
}
And you can put it with http request:
http://localhost:3000/maze?paramName=someValue
Assuming that you have POST request, there may be different ways to build this request, depending on the API testing tools you use.
#RestController follows the same rules for dependency injection as any other #Component in Spring framework.
If you have a single constructor, Spring will try to „inject” the parameters while instantiating the controller.
You need to register your dependency as a Spring bean.
It seems that you are new to Spring and you are starting with advanced topics like Spring Boot and rest controllers. Please find some time to read about the basics.
Yo can create a Bean configuration file to initialize your objects like:
#Configuration
#ComponentScan("com.xxx.xxx") // the base package you want to scan
public class Config {
#Bean
//where Solveable is a class and is annotated with an Spring's annotation
public Solveable solveable() {
return new Solveable();
}
}
And use the #Autowired annotation to inject the object in:
#Autowired
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
This last block will initialize or pass(what you want) the object to the ConnectorService class.

How can I inform my SpringBoot-Application of a class copy?

I've got a problem with #Autowiring being null. I am looking for advice how to model it the spring-boot-way.
My Soap-Services get really big using lots of Repository classes. This gives me a large list of #Autowired already. Now when I want to call a helper-class like HeaderValidator.class I can't instantiate and call it like a POJO. This because everything annotated #Autowiring in my HeaderValidator is null. I can make it work when I add #Autowired at line (1) and remove the content of (2) in SoapServiceImpl.
But this will end in a huge list of #Autowired annotated fields and this looks ugly. I want to prevent this even it works for now.
This Article mentions the #Configurable with AspectJ. But the Article is from 2013 and Spring-Boot has developed since. I tried the #Configurable solution but it didn't work in my case.
How can I inform my SpringBoot-Application of a class copy? Is the #Configurable-way still the only one? Or did I simply model the application wrong?
Application.class:
#SpringBootApplication
public class Application {
private static ApplicationContext ctx;
public static void main(String... args) {
ctx = SpringApplication.run(Application.class, args);
publishSoapServices();
}
SoapService.class (gets published when calling publishSoapServices() in Application.class):
public class SoapServiceImpl implements SoapService {
#Autowired
ProjectRepository projectRepo;
(1) "#Autowired"
HeaderValidator headerValidator;
#Override
public EventReport send(#WebParam(name = "header") HeaderType headerType,
#WebParam(name = "content") ContentType contentType) {
return storeServiceData(headerType, messageType);
}
private EventReport storeServiceData(HeaderType headerType, ContentType contentType) {
projectRepo.save(contentType);
(2) "HeaderValidator headerValidator= new HeaderValidator()"
return headerValidator.validate(headerType);
}
My problem class:
#Service
public class HeaderValidator {
#Autowired
ValidFieldsRepository validFieldsRepo; //<-- always null!
I managed to solve my problem. It was simply due to bad design. I went trough the application and configured #Configurableit correctly. Now it works all fine. Thanks to M.Deinum!

Invoke controller method from java class

I just want to know whether controller class method is accessible from another java class.
following is my controller and its method.
#Controller
public class TestResultUploadController {
#RequestMapping(method = RequestMethod.POST,value="/uploadTestResult")
public #ResponseBody
String uploadTestResult(String testResultBarcode,int deviceLoc) {
//some code goes here
return something;
}
I just want to call this controller method from another java class.
How can I make it work?
please suggest..
Short answer: yes, it is possible. In your other class/thread, you can do
// this will create a new instance of that controller where no fields are wired/injected
TestResultUploadController controller = new TestResultUploadController();
controller.uploadTestResult("someString", 1234);
However, keep in mind that your setup is highly unusual and all your autowired fields wouldn't be wired correctly. If you obtain your controller from the context, you'd be able to have your fields wired/injected properly:
// obtain controller bean from context, should have fields wired properly
TestResultUploadController controller = ctx.getBean(TestResultUploadController.class);
controller.uploadTestResult("someString", 1234);
or you can, in your other class, have:
#Autowired private TestResultUploadController controller;
....
public void doStuff(){
controller.uploadTestResult("someString", 1234);
}
Again, this is highly unusual, but highly possible. However, just cause something is possible to be done, doesn't mean you should do it. I would recommend the more common Spring/MVC approach in which you outsource the business logic to Services. Basically, to have something like this:
#Controller
public class TestResultUploadController {
#Autowired private UploadTestResultService uploadTestResultService;
#RequestMapping(method = RequestMethod.POST,value="/uploadTestResult")
public #ResponseBody String uploadTestResult(String testResultBarcode,int deviceLoc) {
return uploadTestResultService.uploadTestResult(testResultBarcode, deviceLoc);
}
}
And in your thread:
//somehow get the service
UploadTestResultService uploadTestResultService = //somehowGetTheService (whether from context or in some other way)
uploadTestResultService.uploadTestResult(testResultBarcode, deviceLoc);
That way, you'd be able to mock the UploadTestResultService in your tests of the controller, and you'd also be able to test the uploadTestResult method of that service on its own without it being in a controller.
EDIT:
How to obtain the Spring context is outside of the scope of this question. I assume you know basic Spring and also basic java.

Categories

Resources