I'm using vaadin-spring-boot-starter for integration of Vaadin Framework 14 and Spring Boot.
I would like to override the requestStart and requestEnd methods of the SpringServlet class to do the following things:
put stuff such as the current route / view path and current user ID into the SLF4J MDC in order to include it in each logging statement
log the duration of the request
In Vaadin 8 there was a SpringVaadinServlet class which I could replace by simply annotating my custom subclass with #SpringComponent("vaadinServlet").
This approach no longer works. The vaadin-spring integration contains SpringBootConfiguration which contains a direct call to the SpringServlet constructor:
#Bean
public ServletRegistrationBean<SpringServlet> servletRegistrationBean() {
String mapping = configurationProperties.getUrlMapping();
Map<String, String> initParameters = new HashMap<>();
boolean rootMapping = RootMappedCondition.isRootMapping(mapping);
if (rootMapping) {
mapping = VaadinServletConfiguration.VAADIN_SERVLET_MAPPING;
initParameters.put(Constants.SERVLET_PARAMETER_PUSH_URL,
VaadinMVCWebAppInitializer
.makeContextRelative(mapping.replace("*", "")));
}
ServletRegistrationBean<SpringServlet> registration = new ServletRegistrationBean<>(
new SpringServlet(context, rootMapping), mapping); // <-- HERE
registration.setInitParameters(initParameters);
registration.setAsyncSupported(configurationProperties.isAsyncSupported());
registration.setName(
ClassUtils.getShortNameAsProperty(SpringServlet.class));
return registration;
}
They should use a conditional bean here so we could replace it, but unfortunately they're not.
Just adding a custom ServletRegistrationBean with a copy of the above code (but the constructor call substituted with my own) doesn't work, even with #Primary.
So is there a better way to do what I want than to exclude the whole vaadin-spring autoconfiguration and copy everything in my own configuration bean? It works but I have to check if everything's still OK after each vaadin-spring upgrade.
You could add a VaadinServiceInitListener through which you can add a custom request handler. Alternatively you could use a Filter.
Related
I have to use company's custom made libraries with Spring Boot and wondering if I'm able to create bean like this in runtime and add it to Spring application context.
#Bean(name = {"customConnectionFactory"})
public ConnFactory connector() {
return new SimpleConnFactory(configuration(), "prefix");
}
So this worked fine when I was allowed to wire beans normally when starting the application. Now requirements have changed and I should be able to do this dynamically runtime. I've done some research and it seems that it's possible to add class to spring context runtime, but how about running method which returns new object?
Could be something like this
DefaultListableBeanFactory beanFactory = //get and store the factory somewhere
MyBean newBean = new MyBean();
beanFactory.initializeBean(newBean,"TheBeanName"); //could be class' canonical name
beanFactory.autowireBeanProperties(newBean, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
beanFactory.registerSingleton("TheBeanName", newBean);
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'm trying to use interceptors with hibernate in order to create a log table.
The application works fine but my interceptors are never triggered when they were supposed to be triggered.
I tried the solutions of this post on stack overflow and I did not succed in making it work.
What I did is:
create an entity class that will be the table where the log are stored (UserLog)
create another class called by the interceptor that will fill my table (dao), in this example it is called UserLogUtil
I tried the first solution, making my interceptor class not dependant on spring #Autowire by making the UserLogUtil a singleton manually and adding the following line to my application.properties : spring.jpa.properties.hibernate.ejb.interceptor=LogInterceptor.
I also tried the second solution by autowiring UserLogUtil however my spring configuration is in xml and although I tried to convert the beans from the above example it did not work.
This is what my interceptor looks like:
public class LogInterceptor extends EmptyInterceptor {
private UserLogUtil userLogUtil = UserLogUtil.getInstance();
public boolean onSave(Object entity,Serializable id, Object[] state,String[] propertyNames,Type[] types)
throws CallbackException {
if (entity instanceof Owner){
userLogUtil.log(........));
}
return false;
}
I think I missed something in the process of building my interceptor and my design could be flawed. Do you have any idea that could make my interceptor function as it should ?
tl;dr: how to enable spring's ResourceUrlEncodingFilter for spring boot Error pages?
(Question written while using spring boot 1.3.7.RELEASE and Spring Framework/MVC 4.2.4.RELEASE)
Some background: We have a fairly standard spring boot/spring webmvc project using Thymeleaf as the view layer. We have the out-of-the-box spring boot Resource Chain enabled to serve static assets.
Our thymeleaf views have standard url-encoding syntax in them such as <script th:src="#{/js/some-page.js}"></script>. This relies on Spring's org.springframework.web.servlet.resource.ResourceUrlEncodingFilter to transform the url into an appropriately-versioned url such as /v1.6/js/some-page.js.
Our error handling is done by:
setting server.error.whitelabel.enabled=false
subclassing spring boot's default BasicErrorController to override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
relying on our already-configured thymeleaf view resolvers to render our custom error page
The problem is: the ResourceUrlEncodingFilter isn't applying on our error pages. I assume it's a lack of the filter being registered for ERROR dispatched requests, but it's not obvious to me: a) how to customize this in spring boot; and b) why this wasn't done by default.
Update 1:
The issue seems to be with a combination of OncePerRequestFilter and the ERROR dispatcher. Namely:
ResouceUrlEncodingFilter does not bind to the ERROR dispatcher by default. While overriding this is messy it's not impossible, but doesn't help due to:
OncePerRequestFilter (parent of ResourceUrlEncodingFilter) sets an attribute on the Request indicating it's been applied so as to not re-apply. It then wraps the response object. However, when an ERROR is dispatched, the wrapped response is not used and the filter does not re-wrap due to the request attribute still being present.
Worse still, the logic for customizing boolean hasAlreadyFilteredAttribute is not overridable by request. OncePerRequestFilter's doFilter() method is final, and getAlreadyFilteredAttributeName() (the extension point) does not have access to the current request object to get the dispatcher.
I feel like I must be missing something; it seems impossible to use versioned resources on a 404 page in spring boot.
Update 2: A working but messy solution
This is the best I've been able to come up with, which still seems awfully messy:
public abstract class OncePerErrorRequestFilter extends OncePerRequestFilter {
#Override
protected String getAlreadyFilteredAttributeName() {
return super.getAlreadyFilteredAttributeName() + ".ERROR";
}
#Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
}
public class ErrorPageCapableResourceUrlEncodingFilter extends OncePerErrorRequestFilter {
// everything in here is a perfect copy-paste of ResourceUrlEncodingFilter since the internal ResourceUrlEncodingResponseWrapper is private
}
// register the error-supporting version if the whitelabel error page has been disabled ... could/should use a dedicated property for this
#Configuration
#AutoConfigureAfter(WebMvcAutoConfiguration.class)
#ConditionalOnClass(OncePerErrorRequestFilter.class)
#ConditionalOnWebApplication
#ConditionalOnEnabledResourceChain
#ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", havingValue="false", matchIfMissing = false)
public static class ThymeleafResourceUrlEncodingFilterErrorConfiguration {
#Bean
public FilterRegistrationBean errorPageResourceUrlEncodingFilterRegistration() {
FilterRegistrationBean reg = new FilterRegistrationBean();
reg.setFilter(new ErrorPageCapableResourceUrlEncodingFilter());
reg.setDispatcherTypes(DispatcherType.ERROR);
return reg;
}
}
Better solutions?
This has been reported in spring-projects/spring-boot#7348 and a fix is on its way.
It seems you've made an extensive analysis of the issue; too bad you didn't report this issue earlier. Next time, please consider raising those on the Spring Boot tracker.
Thanks!
How do I add things to the /info endpoint in Spring Boot programmatically? The documentation states that this is possible for the /health endpoint through the use of HealthIndicator interface. Is there something for the /info endpoint as well?
I would like to add operating system name and version and other runtime info there.
In Spring Boot 1.4, you are able to declare InfoContributer beans to make this a whole lot easier:
#Component
public class ExampleInfoContributor implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
See http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#production-ready-application-info-custom for more info.
The accepted answer actually clobbers the InfoEndpoint and does not add to it.
One way I found to add to the info is, in a #Configuration class, add an #Autowired method that adds extra properties following the info.* conventions to the environment. Then InfoEndpoint will pick them up when its invoked.
You can do something like the following:
#Autowired
public void setInfoProperties(ConfigurableEnvironment env) {
/* These properties will show up in the Spring Boot Actuator /info endpoint */
Properties props = new Properties();
props.put("info.timeZone", ZoneId.systemDefault().toString());
env.getPropertySources().addFirst(new PropertiesPropertySource("extra-info-props", props));
}
One way to do what you want (in the event that you have totally custom properties you need to display) is to declare a bean of type InfoEndpoint which will override the default.
#Bean
public InfoEndpoint infoEndpoint() {
final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(map);
}
As you can see from the code above, the map can contain whatever info you need.
In the event that the data you want to show can be retrieved by the environment and is not custom, you do not need to override the InfoEndpoint bean, but you can simply add properties to the properties file with a prefix of info. One example where the OS name is evaluated is the following:
info.os = ${os.name}
In the code above, Spring Boot will evaluate the right-hand expression before returning the property in the /info endpoint.
A final note is that there is a ton of environment information that is already available in the /env endpoint
Update
As pointed out by #shabinjo, in newer Spring Boot versions there is no InfoEndpoint constructor that accepts a map.
You can however use the following snippet:
#Bean
public InfoEndpoint infoEndpoint() {
final Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(new MapInfoContributor(map));
}
The code above will completely override the default info that would end-up in /info.
To overcome this issue one could add the following bean
#Bean
public MapInfoContributor mapInfoContributor() {
return new MapInfoContributor(new HashMap<String, Object>() {{
put("test", "value");
}});
}
It should be possible to add a custom PropertySource inside an ApplicationListener to add custom info.* properties to the environment (see this answer for an example: How to Override Spring-boot application.properties programmatically)