Using Spring Boot 1.0, I was able to customize the actuator endpoints as follows...
endpoints.beans.id=foo/springbeans
This would expose the spring beans endpoint at /foo/springbeans. However, in the latest Spring Boot this is not possible due to the following code in the AbstractEndpoint...
#NotNull
#Pattern(regexp = "\\w+", message = "ID must only contains letters, numbers and '_'")
private String id;
I tried using the underscore, but that just exposes the endpoint at /foo_springbeans. This lead me to try to add a view controller so I could at least redirect or forward to the default endpoint, but I couldn't find an easy way to do that either. How can I configure the endpoint or a redirect?
After opening an issue with Spring Boot and being told to simply move the entire management context as suggested by Rafal, I was able to achieve what I was looking for, albeit with more code than I'd like. I created a custom MvcEndpoint as follows...
#Named
public class MyCustomHealthCheck extends EndpointMvcAdapter {
private HealthEndpoint delegate;
#Inject
public MyCustomHealthCheck(HealthEndpoint delegate) {
super(delegate);
this.delegate = delegate;
}
#ResponseBody
#RequestMapping(value = "/springbeans", method = GET)
public Health foo() {
return delegate.invoke();
}
}
The code above creates the /springbeans path underwhatever path the HealthEndpoint is mapped to, which is fine enough for my usecase. If I wanted it mapped to an entirely separate path, I would have needed to create a dummy endpoint and stick this MvcEndpoint under that.
For Spring 1.x Following property should help you:
endpoints.beans.path: /foo/springbeans
You can use it with any standard endpoint and if you want to use it with custom endpoint that extends AbstractEndpoint then you need additional annotation:
#ConfigurationProperties(prefix = "endpoints.customEndpoint")
and then use property:
endpoints.customEndpoint.path: /custom/endpoint
Related
I'm building a Quarkus app which handles http requests with resteasy and calls another api with restclient and I need to propagate a header and add another one on the fly so I added a class that implements ClientHeadersFactory.
Here's the code:
#ApplicationScoped
public abstract class MicroServicesHeaderHandler implements ClientHeadersFactory {
#Inject
MicroServicesConfig config;
#Override
public MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders) {
// Will be merged with outgoing headers
return new MultivaluedHashMap<>() {{
put("Authorization", Collections.singletonList("Bearer " + config.getServices().get(getServiceName()).getAccessToken()));
put("passport", Collections.singletonList(incomingHeaders.getFirst("passport")));
}};
}
protected abstract String getServiceName();
My issue is that the injection of the config doesn't work. I tried both with #Inject and #Context, as mentioned in the javadoc of ClientHeadersFactory. I also tried to make the class non abstract but it doesn't change anything.
MicroServicesConfig is a #Startup bean because it needs to be initialized before Quarkus.run() is called, otherwise the hot reload doesn't work anymore, since it's required to handle requests.
Here's the code FYI:
#Getter
#Startup
#ApplicationScoped
public final class MicroServicesConfig {
private final Map<String, MicroService> services;
MicroServicesConfig(AKV akv, ABS abs) {
// some code to retrieve an encrypted file from a secure storage, decrypt it and initialize the map out of it
}
It appears to be an issue with ClientHeadersFactory because if I inject my bean in my main class (#QuarkusMain), it works. I'm then able to assign the map to a public static map that I can then access from my HeaderHandler with Application.myPublicStaticMap but that's ugly so I would really prefer to avoid that.
I've searched online and saw several people having the same issue but according to this blogpost, or this one, it should work as of Quarkus 1.3 and MicroProfile 3.3 (RestClient 1.4) and I'm using Quarkus 1.5.2.
Even the example in the second link doesn't work for me with the injection of UriInfo so the issue doesn't come from the bean I'm trying to inject.
I've been struggling with this for weeks and I'd really like to get rid of my workaround now.
I'm probably just missing something but it's driving me crazy.
Thanks in advance for your help.
This issue has finally been solved in Quarkus 1.8.
I'm integrating spring web sockets capability into an existing spring mvc application, everything works as expected, except for enabling custom Spring Conversion on my inbound messages via #DestinationVariable.
Now I already have custom converters fully working for the http side, ex #RequestParam or #PathVariable but the same conversion on a websocket controller method throws a ConverterNotFoundException
Ex. I have a custom converter that converts String into Users
public class StringToUserConverter implements Converter<String,User>{
#Autowired UserDAO userDAO;
#Override
public User convert(String id) {
return userDAO.getUser(Integer.parseInt(id));
}
}
And this works exactly as expected in my http controllers, where I can pass in an id, and its automatically converted to the domain class
public String myControllerMethod(#RequestParam User user)
However the same does not work for my websocket controller for a parameter annotated with #DestinationVariable
#MessageMapping("/users/{user}")
#SendTo("/users/greetings")
public String send(#DestinationVariable User user) {
return "hello"
}
I stepped through the code and I can see that the DestinationVariableMethodArgumentResolver has the default conversion service which doesnt include my custom coverters
So how do I register custom converters, or a custom ConversionService so that it works for web sockets like it already does for http controllers
So now I'm running into the same issue with #Header annotation for JmsListener methods.
Same idea, #Header User user, throws the ConverterNotFound exception.
#JmsListener(destination = "testTopic")
public void testJmsListener(Message m, #Header User user)..
Here I was trying to pass the user id on the message header, and have spring convert it, but to no avail, only basic default conversions are supported, like strings or numbers.
I have stepped through quite a bit of initialization code in Spring here, and I can see that a new DefaultConversionService gets instantiated in many places, without any consideration for external configuration.
It looks like these modules are not nearly as mature as Spring MVC or the developers took a shortcut. But based on my inspection there is no way to easily configure custom converters.
Ok and here is the very hacky, not recommended, approach that did work. Its pretty convoluted and brittle, Im not going to use it, but just for illustration purposes here is what it took to register a custom converter for #Header jms mapping.
Here Im passing in a user_email on the jms message header, and wanted spring to automatically convert the id/email into the actual domain object User. I already had a working converter that does this well in mvc/http mode.
public class StringToUserConverter implements Converter<String,User>{
#Autowired
UserDAO userDAO;
public User convert(String email) {
return userDAO.getByEmail(email);
}
}
The above part is pretty standard and straight forward. Here comes the idiotically convoluted part. I stepped through the spring jms listener initialization code and found lowest spot where I could cut-in with my custom converter for jms #Header.
I created a service, that will #Autowire one of springs Jms beans, and then sets a custom conversion service on it using #PostConstruct. Even here some of the properties were private, so I had to use reflection to read them
#Service
public class JmsCustomeConverterSetter {
#Autowired
StringToUserConverter stringToUserConverter;
#Autowired
JmsListenerAnnotationBeanPostProcessor jmsPostProcessor;
#PostConstruct
public void attachCustomConverters() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
//create custom converter service that includes my custom converter
GenericConversionService converterService = new GenericConversionService();
converterService.addConverter(stringToUserConverter); //add custom converter, could add multiple here
DefaultConversionService.addDefaultConverters(converterService); //attach some default converters
//reflection to read the private field so i can use it later
Field field = jmsPostProcessor.getClass().getDeclaredField("beanFactory"); //NoSuchFieldException
field.setAccessible(true);
BeanFactory beanFactory = (BeanFactory) field.get(jmsPostProcessor); //IllegalAccessException
DefaultMessageHandlerMethodFactory f = new DefaultMessageHandlerMethodFactory();
f.setConversionService(converterService);
f.setBeanFactory(beanFactory); //set bean factory read using reflection
f.afterPropertiesSet();
jmsPostProcessor.setMessageHandlerMethodFactory(f);
}
}
Creating the DefaultMessageHandlerMethodFactory was based on code I saw in org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory.
I would definitely not recommend using this in production. It is fairly brittle and unnecessarily complex.
Spring...sometimes it's a breath of fresh air... and sometimes it's convoluted-clap-trap
I have a straightforward test case. I have a controller which has a parameter of a type Spring doesn't support by default, so I wrote a custom resolver.
I create the mock mvc instance I'm using like so:
mvc = MockMvcBuilders.standaloneSetup(controller).setCustomArgumentResolvers(new GoogleOAuthUserResolver()).build();
However, Spring is also registering almost 30 other argument resolvers, one of which is general enough that it is getting used to resolve the argument before mine. How can I set or sort the resolvers so that mine is invoked first?
This worked for me without reflection:
#RequiredArgsConstructor
#Configuration
public class CustomerNumberArgumentResolverRegistration {
private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#PostConstruct
public void prioritizeCustomArgumentResolver () {
final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(Objects.requireNonNull(requestMappingHandlerAdapter.getArgumentResolvers()));
argumentResolvers.add(0, new CustomerNumberArgumentResolver());
requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
}
}
The issue was that the People class the Google OAuth library I am using extends Map and the mock servlet API provides no way to manipulate the order in which the handlers are registered.
I ended up using reflection to reach into the mocks guts and remove the offending handler.
I have requirement for spring mvc 3 caching. Requirement is : while starting the server, we need to call database for one dropdown and put those values in the cache. So that whenever we required those values, we need to retrieve from cache.
Please help me with an example.
Thanks in advance.
May be you can use init-method (Spring 2.5) or #PostConstruct annotation (in Spring 3.0).
This method will be called during server start up
The following is code snippet
#Component
public class CacheDBData {
private String values[];
//add setter & getter
//This will be called during server start up after properties are initialised
#PostConstruct
public void getDataFromDB() {
values = //Logic to get data from DB and store that in values property
}
}
Suppose for example you can use in class as follows
#controller
public class HomeController {
#Autowired
private CacheDBData cacheDBData ;
//getter and setters
private void methodxyz() {
String values[] = cacheDBData.getValues();
}
}
I've had success with Ehcahe for Spring. There's a couple of config files to setup but after that you simply annotate the methods you want to cache the output from and it just works.
This has the advantage that you can change the values coming back from the service/database and NOT have to restart your app, unlike the accepted answer.
Just came accross this today in a Spring MVC cotnroller class,
#RequestMapping(value = { "/foo/*" }, method = { RequestMethod.GET})
private String doThing(final WebRequest request) {
...
return "jsp";
}
This is making it a bit harder to write a test, I'll probably change it to public but what's the point of allowing mappings on private methods?
Java does not provide a mechanism for limiting the target of annotations based on access modifier.
As #smp7d stated, Java does not limit the target of annotations based on access modifiers, but syntactically speaking, #RequestMapping should not work on private methods. Also we cannot limit this, since it would break the backward compatibility. So, you can either go for defining your methods as public or you can create your own custom implementation.
Take a look at this: Spring's #RequestMapping annotation works on private methods