I'm moving an existing spring (3.1.1) web mvc Controller (called LoginController) to using annotations, I had
<bean id="loginHandlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="licenseInterceptor" />
<ref bean="propertyInterceptor" />
<ref bean="localeChangeInterceptor" />
</list>
</property>
<property name="urlMap">
<map>
<!-- used to include references to my LoginController -->
<entry key="error" value-ref="error" />
</map>
</property>
<property name="order">
<value>1</value>
</property>
</bean>
I changed my LoginController to be annotated. Some other classes had also been annotated previously so it will use the existing...
<bean id="requestMappingHandlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<list>
<ref bean="licenseInterceptor" />
<ref bean="loginInterceptor" />
<ref bean="propertyInterceptor" />
</list>
</property>
</bean>
LoginController cannot use the loginInterceptor that others use however as it's a pre-login Controller but a post-login Interceptor.
What I want to know, is there a way to tell Spring that this specific Controller should NOT be used with a specific (loginInterceptor) Interceptor? And perhaps if it (and only it) could also use localeChangeInterceptor.
What have I tried
(works in Spring 3.2) Adding <mvc:interceptors> and their namespace to config but they don't seem to allow multiple bean references and exclude-mapping is Spring 3.2, I'm 3.1.1
Doing the processing in LoginInterceptor, handler is not of type LoginController - I can do ((HandlerMethod)handler).getBean() instanceof LoginController and that works but it's not pretty or flexible.
using the spring mvcnamespace you could do the following:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<ref bean="loginInterceptor"/>
</mvc:interceptor>
<!-- .. further interceptors -->
</mvc:interceptors>
this allows to add paths that should not be intercepted by a specific interceptor.
add the mvc namespaces to your configuration root element..
xmlns:mvc="http://www.springframework.org/schema/mvc"
... and the schema ...
xsi:schemaLocation=" .....
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
...."
I've done this in the past by implementing it in the preHandle method in a HandlerInterceptorAdapter.
#Override
public final boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// inspect handler object to see if it's LoginController
}
Here is what it took to get this working in the LoginController. A little like the solution of #blank but with some other nonsence, I'd still like to have a spring (annotation or config) way of fixing this though
public final boolean preHandle(HttpServletRequest request)
{
if (handler instanceof HandlerMethod &&
((HandlerMethod)handler).getBean() instanceof LoginController)
{
return true;
}
...
}
Related
This is a little frustrating... I had this working before in my previous projects, but unable to get it working in my new project after debugging for several hours.
Let's assume I have a simple Rest controller that returns Joda's LocalDate:-
#RestController
#RequestMapping(value = "/api")
public final class ApiController {
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<LocalDate> main() {
return new ResponseEntity<LocalDate>(LocalDate.now(), HttpStatus.OK);
}
}
By default, when I call http://app/api, I get [2015,10,13]. What I really want is 2015-10-13.
To solve this in my previous project, I got it working with this configuration in spring-servlet.xml:-
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<context:component-scan base-package="test.controller"/>
<mvc:annotation-driven/>
<mvc:resources location="/resources/" mapping="/resources/**"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="objectMapper"
class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd'T'HH:mm:ss.SSSZ">
</bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"
p:targetObject-ref="objectMapper"
p:targetMethod="registerModule">
<property name="arguments">
<list>
<bean class="com.fasterxml.jackson.datatype.joda.JodaModule"/>
</list>
</property>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
But, when I do the same thing in my new project, I'm getting [2015,10,13] again instead of 2015-10-13.
I did upgrade some dependencies and I also make sure there's no additional ObjectMapper being loaded.
Here's my current dependency tree... I removed all the things that I don't need:-
How do I configure Spring MVC to return the correct date format in JSON?
Thank you very much.
You declared <mvc:annotation-driven> twice. Try removing the first declaration (empty, default config). Probably the message converter you configured in the second <mvc:annotation-driven> is getting overriden by the first declaration (with default message converters).
I am trying to return a object from my controller which should be parsed to xml by spring.
But I used the #XmlNamedObjectGraph (from moxy eclipselink) annotation in my class to customize the returned object. So I have to set the property MarshallerProperties.OBJECT_GRAPH from the marshaller.
How can I access the marshaller, which is used by spring to parse my object, in my controller?
ie:
#RequestMapping(value = "/xml/", method = RequestMethod.GET, produces = "application/xml")
#ResponseBody
public ResponseEntity<Customer> getXml() {
Customer customer = _customerService.getById(12);
...
marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "default");
...
return new ResponseEntity<>(customer, HttpStatus.OK);
}
Thanks for your help in advance.
It is like Sotirios Delimanolis said. You have to implement your own AbstractJaxb2HttpMessageConverter. But additional to that you have implementet an WebBindingInitializer and register it with:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="com.example.CommonWebBindingInitializer" />
</property>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter" />
<bean class="com.example.Jaxb2RootElementHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
You'll need to implement your own AbstractJaxb2HttpMessageConverter class and override its createMarshaller method to provide a Marshaller with your own properties. Look at Jaxb2RootElementHttpMessageConverter for implementation hints.
Once you've implemented such a class, you'll need to register it as a HttpMessageConverter with your MVC stack. If you're doing your configuration through Java, look into WebMvcConfigurationSupport#configureMessageConverters(..). If you are doing it through XML, look into
<mvc:annotation-driven>
<mvc:message-converters>
<!-- bean goes here -->
</mvc:message-converters>
</mvc:annotation-driven>
If you need to customize the marshaller, create a marshalling view and configure the marshaller with the properties you need, this is an example of configuring a JAXB marshaller (see this answer):
<!-- XML view using a JAXB marshaller -->
<bean id="jaxbView" class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.company.AClass</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
<!-- Resolve views based on string names -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
In Spring 3.0.5 and before, it was possible to create a base controller test class that fetched the HandlerMapping object from the Spring Application Context to directly access and call into a controller's method via the URL. I found this approach to be really awesome because in addition to testing the controller's methods, I was also testing the path variables and such in my unit/integration tests.
With Spring 3.2.4, this approach appears not to be possible any longer due to the restructuring of how Spring deals with Url mappings. I see that Spring provides a new MVC test framework, but to be honest, I think it's design is too verbose and looks nothing like the rest of the framework or my application code. It also doesn't play nicely with intellisense features in IntelliJ. To be honest, I'd rather not use it.
So, is there an alternative way to test controller URL's that does not use the new Spring MVC test framework, like I was doing before? I have an existing project with 371 controller tests, and I'd REALLY like to avoid migrating everything over to use the Spring MVC test framework.
Here is the handle() method I was using to test controllers using Spring 3.0.5:
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
final HandlerInterceptor[] interceptors = handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
return handlerAdapter.handle(request, response, controller);
}
protected ModelAndView handle(String method, String path, String queryString) throws Exception {
request.setMethod(method);
request.setRequestURI(path);
if(queryString != null) {
String[] parameters = queryString.split("&");
for(String parameter : parameters) {
String[] pair = parameter.split("=");
if(pair.length == 2) {
request.setParameter(pair[0], pair[1]);
} else {
request.setParameter(pair[0], "");
}
}
}
return handle(request, response);
}
protected ModelAndView handle(String method, String path, String attribute, Object object) throws Exception {
MockHttpSession session = new MockHttpSession();
session.setAttribute(attribute, object);
request.setSession(session);
return handle(method, path, null);
}
protected ModelAndView handle(String method, String path) throws Exception {
return handle(method, path, null);
}
Here is some test code illustrating how I was using the handle() method:
#Test
public void show() throws Exception {
ModelAndView modelAndView = handle("GET", "/courseVersion/1/section/1");
Section section = (Section) modelAndView.getModel().get("section");
assertEquals(1, section.getId());
}
Here is my servlet application context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:applicationContext.properties"/>
</bean>
<bean id="expressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
</bean>
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<context:component-scan base-package="keiko.web.controllers"/>
<mvc:annotation-driven validator="validator" />
<mvc:interceptors>
<bean class="keiko.web.interceptors.IpValidationInterceptor" />
<bean class="keiko.web.interceptors.UnreadMessagesInterceptor" />
<bean class="keiko.web.interceptors.ThemeInterceptor" />
<bean class="keiko.web.interceptors.ApplicationMenuInterceptor" />
</mvc:interceptors>
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerSettings">
<props>
<prop key="auto_import">lib/common.ftl as common, lib/layouts.ftl as layouts</prop>
<prop key="whitespace_stripping">true</prop>
</props>
</property>
<property name="freemarkerVariables">
<map>
<entry key="template_update_delay" value="0"/>
<entry key="default_encoding" value="ISO-8859-1"/>
<entry key="number_format" value="0.##"/>
<entry key="xml_escape">
<bean class="freemarker.template.utility.XmlEscape"/>
</entry>
</map>
</property>
</bean>
<bean id="contentNegotiatingViewResolver"
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1"/>
<property name="ignoreAcceptHeader" value="true" />
<property name="defaultContentType" value="text/html" />
<property name="mediaTypes">
<map>
<entry key="html" value="text/html"/>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="useNotAcceptableStatusCode" value="true" />
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="contentType" value="text/html" />
<property name="order" value="2"/>
<property name="cache" value="${freemarker.cache}"/>
<property name="prefix" value=""/>
<property name="suffix" value=".ftl"/>
<property name="exposeSpringMacroHelpers" value="true"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="contentType" value="application/json" />
</bean>
</list>
</property>
</bean>
<bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1000000"/>
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="keiko.domain.courseauthor.SectionIsDelayedException">error/sectionIsDelayed</prop>
<prop key="keiko.service.director.CompanyHomepageClosedException">error/registrationClosed</prop>
<prop key="keiko.service.director.IpDeniedException">error/ipDenied</prop>
</props>
</property>
</bean>
</beans>
Basically your test method has been flawed basically from the start. There was always a possibility that there where more then 1 HandlerMapping and 1 HandlerAdapter. What you are basically doing is mimic the DispatcherServlet.
What you should do is lookup all HandlerMappings and HandlerAdapters and check if one of them has a match for the URL (i.e. returning a HandlerExecutionChain) and select the appropriate HandlerAdapter (calling the supports method). What you are doing is basically what the DispatcherServlet is doing.
Here is my locale configuration
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang" />
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="en"/>
</bean>
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<ref bean="localeChangeInterceptor" />
</property>
</bean>
When I try to call locale in a controller using
#RequestMapping(value = "customers/customer-{idCustomer:[0-9]+}/detail", method = RequestMethod.GET)
public ModelAndView detail(Map<String, Object> map, #PathVariable Integer idCustomer, Locale locale) {
logger.info(locale.toString());
logger.info(request.getLocale().toString());
...
}
It returns different values. But when I switch a language on a site using GET param in URL ?lang=en, it change nothing in mentioned controller's calls. i18n works fine, it loads a labels from correct file. But I want to obtain changed language in my controllers. I want to obtain choosed language independently on opened page (with/without request param lang in URL).
You can use LocaleContextHolder class that Spring provides for this purpose. From documentation:
Used as a central holder for the current Locale in Spring, wherever
necessary: for example, in MessageSourceAccessor. DispatcherServlet
automatically exposes its current Locale here. Other applications can
expose theirs too, to make classes like MessageSourceAccessor
automatically use that Locale.
Then in your controller just call:
LocaleContextHolder.getLocale();
to retrieve the locale using Spring.
LocaleContextHolder.getLocale() javadoc.
I have a Spring 2.5.x application which I'm migrating to Spring 3 and just bumped into a little problem.
I have an handler mapping like so:
<bean id="handlerMappings1" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="interceptor1" />
<ref bean="interceptor2" />
....
<ref bean="interceptorN" />
</list>
</property>
<property name="urlMap">
<map>
<entry key="/url1.html" value-ref="controller1" />
<entry key="/url2.html" value-ref="controller2" />
....
<entry key="/url100.html" value-ref="controller100" />
</map>
</property>
</bean>
and another one like this:
<bean id="handlerMappings2" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/urlA.html" value-ref="controllerA" />
<entry key="/urlB.html" value-ref="controllerB" />
....
<entry key="/urlN.html" value-ref="controllerN" />
</map>
</property>
</bean>
I'm slowly replacing both with #RequestMapping annotations with a <context:component-scan> (which basically registers a DefaultAnnotationHandlerMapping).
In Spring 3 I saw the <mvc:interceptors> tag which can be used to add interceptors to certain URLs but you can specify only one interceptor, at least that's what I see from the schema.
From what I can figure, I have to register one of these for each interceptor which will duplicate all my URLs for as many times as I have interceptors (and I don't even know in what order they will run).
On the other hand I can't add the iterceptors on the DefaultAnnotationHandlerMapping because they will run for all my controllers annotated with #RequestMapping and I don't want that.
So how can I specify interceptors is Spring 3 for some URLs, without repeating the URL's and
keeping the URL to controller mapping based on the #RequestMapping annotation?
You could have a look at the SelectedAnnotationHandlerMapping and the IgnoreSelectedAnnotationHandlerMapping classes from the springplugins project. The sources are some years old but the idea still stands.
There is a presentation on the creator's blog here: Spring Framework Annotation-based Controller Interceptor Configuration. Make sure you also read the comments to the blog post.
One option would be to create a custom interceptor which can delegate to a collection of injected interceptors.