Adding the line spring.data.rest.basePath=/api to my application.properties file makes it so every endpoint starts with /api.
On top of that, I want each controller to "increment" this url. For example, assume I have 2 different controllers, CustomerController and ProviderController.
If I define in both this function:
//CustomerController
#Autowired
private CustomerService service;
#GetMapping("/getById/{id}")
public Customer findCustomerById(#PathVariable int id) {
return service.getCustomerById(id);
}
//ProviderController
#Autowired
private ProviderService service;
#GetMapping("/getById/{id}")
public Provider findProviderById(#PathVariable int id) {
return service.getProviderById(id);
}
I want the first one to be /api/customer/getById/{id} and the second /api/provider/getById/{id}.
Is there any way to achieve this without manually having to type it on each annotation?
Thank you.
Yes, you can extract the common part of the path and put it into #RequestMapping on your controller:
#RestController
#RequestMapping("/api/customer")
public class CustomerController {
// ...
#GetMapping("/getById/{id}")
public Customer findCustomerById(#PathVariable int id) {
return service.getCustomerById(id);
}
}
and
#RestController
#RequestMapping("/api/provider")
public class ProviderController {
// ...
#GetMapping("/getById/{id}")
public Provider findProviderById(#PathVariable int id) {
return service.getProviderById(id);
}
}
You can use the #RequestMapping("/example/url") Annotation on your controller.
#Controller
#RequestMapping("/url")
class HomeController() {}
Related
I am working on an application with a WebSocket and want to save the clients id and session to a manager but have difficulties to understand how to do this correct when I also want to be able to reach this from another class with autowire.
public class Client {
private String id;
private Session session;
private MessageHandler handler;
Client(String id, Session session, MessageHandler handler) {
this.id = id;
this.session = session;
this.handler = handler;
}
}
public class ClientsManager {
private Set<Client> clientSet = new CopyOnWriteArraySet<>();
public Set<Client> getClients() {
return this.clientSet;
}
public void addClient(Client client) {
this.clientSet.add(client);
}
public void removeClient(Client client) {
clientSet.remove(client);
}
}
public class WebsocketServerEndpoint {
public static final ClientsManager manageClients = new ClientsManager();
#OnOpen
public void onOpen(Session session, #PathParam("connectId") String connectId) throws IOException, EncodeException {
MessageHandler messageHandler = new MessageHandler();
Client client = new Client(connectId, session, messageHandler);
this.client = client;
manageClients.addClient(client);
}
....
....
....
....
}
From another class:
public class DoSomething {
#Autowired
WebsocketServerEndpoint serverEndpoint;
public String doSomething() {
int numberOfClients = serverEndpoint.getClients().size()
return numberOfClients;
}
}
As I understand. This is not correct and you should not autowire static fields and so.
I can see when I debug that serverEndpoint: null in my DoSomething class but I get 1 connected client if I have one connected and so on.
When I do like this I will get the right number of clients in DoSomething class.
Have I just misunderstood this and it works as I have done?
or how should I do instead?
Is their a better way to write my Client and ClientsManager classes?
What I have read that if I would like to "Autowire" anyway there is two possible ways.
Using Constructor #Autowired For Static Field
Using #PostConstruct to set the value to Static Field
But how does this work when I would like to instantiate "public static final ClientsManager manageClients = new ClientsManager();"
Sorry for my stupid question but I feel I do not fully understand this.
If you would like to understand more about this topic search for Spring Dependency injection, but I write a short summary.
To be able to #Autowire a component you have to create a #Bean or #Service or #Component.
Creating beands first create a Configuration class, and a Beand or Beans inside.
#Configuration
public class Configuration {
#Value("${configuration.property.name}")
String username;
#Bean
public WebsocketServerEndpoint ebsocketServerEndpoint () {
return new WebsocketServerEndpoint();
}
}
#Value is not necessaty just good to mention with this annotation you can get a property name from spring application.properties file.
After this point you have created a #Bean instance of your class it is registered as a singleton class. You can get this one copy class from anywhere in your application you just have to, autowire it.
Or user construcor based dependency injection. ( #Autowired is not prefered).
Dont create beans just add #Component annotation to your class that you want to Autowire but I show a constructor injection.
#Component
public class WebsocketServerEndpoint {
public String test(){
return "test";
}
}
#RestController
public class DoSomething {
private final WebsocketServerEndpoint websocketHandler;
public DoSomething(WebsocketServerEndpoint websocketHandler) {
this.websocketHandler = websocketHandler;
}
#GetMapping(value = "/test")
public String test() {
return websocketHandler.test();
}
}
You can even test this endpoint with a curl GET request. curl http://localhost:8080/test
Does anyone know if I should be able to use property placeholder as an expression in a Qualifier? I can't seem to get this working.
I am using spring 3.0.4.
#Controller
public class MyController {
#Autowired
#Qualifier("${service.class}")
Service service;
}
#Service
#Qualifier("ServiceA")
ServiceA implements Service {
public void print() {
System.out.println("printing ServiceA.print()");
}
}
#Service
#Qualifier("ServiceB")
ServiceB implements Service {
public void print() {
System.out.println("printing ServiceB.print()");
}
}
XML:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:/etc/config.properties"/>
</bean>
config.properties:
config.properties
service.class=serviceB
This works. You can leave off the service names if you just use the default spring bean name. serviceA vs ServiceA, etc.
#Controller
class MyController {
#Autowired(required=false)
#Qualifier("Service")
Service service;
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("app-ctx.xml", MyController.class);
for(String s:context.getBeanDefinitionNames()){
System.out.println(s);
for(String t:context.getAliases(s)){
System.out.println("\t" + t);
}
}
context.getBean(MyController.class).service.print();
}
}
public interface Service {
void print();
}
#Service(value="ServiceA")
public class ServiceA implements example.Service {
public void print() {
System.out.println("printing ServiceA.print()");
}
}
#Service(value="ServiceB")
public class ServiceB implements example.Service {
public void print() {
System.out.println("printing ServiceB.print()");
}
}
XML:
<beans>
<alias name="${service.class}" alias="Service"/>
<context:property-placeholder location="example/app.properties"/>
<context:component-scan base-package="example"/>
<beans>
Props:
service.class=ServiceB
This solution works without XML and with properties file.
Yours classes improved:
MyController.java:
#Controller
public class MyController {
#Autowired
public MyController(#Qualifier("MyServiceAlias") MyService myService) {
myService.print();
}
}
ServiceA.java:
#Service("serviceA")
public class ServiceA implements MyService {
#Override
public void print() {
System.out.println("printing ServiceA.print()");
}
}
ServiceB.java:
#Service("serviceB")
public class ServiceB implements MyService {
#Override
public void print() {
System.out.println("printing ServiceB.print()");
}
}
application.properties (here you can change which class will be loaded):
service.class=serviceA
And important configuration file AppConfig.java:
#Configuration
public class AppConfig {
#Autowired
private ApplicationContext context;
#Bean
public MyService MyServiceAlias(#Value("${service.class}") String qualifier) {
return (MyService) context.getBean(qualifier);
}
}
Additional explanations:
Use #Qualifier only for field which will be autowired. For services, to specify bean name, use #Service.
If you want standard bean name you don't need to use #Service with specyify name. For example, standard bean name for ServiceA is serviceA (not ServiceA - see big first letter), so #Service("serviceA") redundant (#Service is enough).
I based AppConfig on this answer: Spring Bean Alias in JavaConfig.
This solution is better than this Spring Qualifier and property placeholder, because you don't need XML.
Tested on Spring Boot 1.5.7.
I would venture to guess the answer is no, just based on the write ups in a few javadoc pages. For example, see the docs for #Value:
http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/beans/factory/annotation/Value.html
Notice they make special mention of using expressions in the annotation. For comparison, the docs for #Qualifier:
http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/beans/factory/annotation/Qualifier.html
Which make no mention of expressions. Obviously not a definitive answer (but spring is generally very good on documentation). Also, if expressions were supported in the #Qualifier annotation I would expect they work the same way as the #Value annotation (just based on spring being a very consistent framework).
Spring 3.1 has the new profile bean feature, which seems like it can accomplish something like what you're trying to do. Here's a write up for that:
http://blog.springsource.com/2011/02/14/spring-3-1-m1-introducing-profile/
As a workarround, you can set the desired Spring service implementation based on its name in your config.properties.
#Controller
public class MyController {
//add a String which will hold the name of the service to implement
#Value("${service.class}")
private String serviceToImplement;
Service service;
// now autowire spring service bean based on int name using setter
#Autowired
public void setService(ApplicationContext context) {
service = (Service) context.getBean(serviceToImplement);
}
}
#Service
#Qualifier("ServiceA")
ServiceA implements Service {
public void print() {
System.out.println("printing ServiceA.print()");
}
}
#Service
#Qualifier("ServiceB")
ServiceB implements Service {
public void print() {
System.out.println("printing ServiceB.print()");
}
}
config.properties
service.class=serviceB
Maybe give this a whirl:
#Controller
public class MyController {
private String serviceId;
#Value("${serviceId}")
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
#Autowired
#Qualifier(serviceId)
Service service;
}
Is there a way to transform a Spring Security Principal before it is injected in a RestController method?
Let's say I have defined the following class:
#RestController
public class MyController {
#GetMapping("/test")
public void getWithPrincipalA(#AuthenticationPrincipal PrincipalTypeA a) {
...
}
#GetMapping("/test")
public void getWithPrincipalB(#AuthenticationPrincipal PrincipalTypeB b) {
...
}
}
I know that these controller methods are ambiguous and I could do several things to solve that, but what I would rather do is transform the #AuthenticationPrincipal to some type I can define myself. The result would become something like:
#RestController
public class MyController {
#GetMapping("/test")
public void getWithTransformedPrincipal(#AuthenticationPrincipal MyTransformedPrincipal principal) {
...
}
}
Now I basically could define a single controller for several different authentication principals, without having to change the API.
Any help would be appreciated :)
Too keep things simple and transparant you could simply transform the principal in your controller method and dispatch the generic principal from there.
#RestController
public class MyController {
#GetMapping("/test")
public void getWithTransformedPrincipal(#AuthenticationPrincipal Principal principal) {
GenericPrincipal generic = PrincipalTransformer.transform(principal);
doSomethingWithPrincipal(generic);
}
}
I have a class annotated with #RestController. Inside that class I have an inner class, which is again annotated with #RestController. Is this fine or are there any unintended side-effects with regards bean creation (using Spring)?
#RestController
#RequestMapping("/api/v1/internal")
public class ClientController {
#GetMapping("/clients/{id}")
public ClientDTO.OutDetail findOne(#PathVariable String id) {
return clientService.findOne(id, ClientDTO.OutDetail.class);
}
#RestController
#RequestMapping("/api/v1/external")
public class ExternalApi {
#GetMapping("/clients/{id}")
public ClientDTO.OutDetailExt findOne(#PathVariable String id) {
return clientService.findOne(id, ClientDTO.OutDetailExt.class);
}
}
}
You can do this. First remove public from inner class and now your URL will be http://ip:port/appName/api/v1/external/clients/{id}. But my suggestion is that please create different RestController class so you can easily track it.
i have spring MVC controller
#Controller
#RequestMapping({ "/user/limits" })
public class UserController {
#Value("${wsgServiceURL}")
private String wsgServiceURL;
.
.
which populate wsgServiceURL value from property file
is that possible to run validation code on that value before population
Yes it is possible using type safe configuration properties through the #ConfigurationPropertiesmechanism
#Controller
#RequestMapping({ "/user/limits" })
#ConfigurationProperties("uc")
public class UserController {
// will map to uc.wsgServiceURL in property file
private String wsgServiceURL;
You can also add validation with #Validated and use use JSR-303 javax.validation
you can do something like the below,
#Controller
#RequestMapping({ "/user/limits" })
public class UserController {
private String wsgServiceURL;
#Autowired
public void initProperty(#Value("${wsgServiceURL}") String wsgServiceURL) {
if(wsgServiceURL== null) {
// Error handling here
}
}
}