InputStream
/ as URL
? org.springframework.beans.propertyeditors.InputStreamEditor
, which allows conversion from String → org.springframework.core.io.Resource → java.io.InputStream
. If you have e.g. overloaded constructors, one of which has String
argument, then you need to specify the target type explicitly:<bean class="..."> <constructor-arg value="classpath:/com/company/module/file.xml" type="java.io.InputStream" /> </bean>
The same conversion is also possible via build-in org.springframework.beans.propertyeditors.URLEditor
, which allows conversion from String → org.springframework.core.io.Resource → java.net.URL
:
<bean class="..."> <constructor-arg value="classpath:/com/company/module/file.xml" type="java.net.URL" /> </bean>
import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; for (Resource resource : new PathMatchingResourcePatternResolver().getResources("classpath*:/path/*.txt")) { resource.getInputStream(); }
<bean ... p:fieldName="#{null}" />
<bean id="cacheManager" class="util.LRUMapCacheManager" />
Property
/ as ResourceBundle
? PropertiesFactoryBean
which allows you to load properties from text or XML file(s) and optionally merge with fixed values:<bean class="..."> <constructor-arg> <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="location" value="classpath:/com/company/module/list_en.properties" /> </bean> </constructor-arg> </bean>
or if you need to inject java.util.ResourceBundle
type you may use org.springframework.beans.propertyeditors.ResourceBundleEditor
(which is not registered in Spring context by default, so one need to make it manually):
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.util.ResourceBundle"> <bean class="org.springframework.beans.propertyeditors.ResourceBundleEditor" /> </entry> </map> </property> </bean> <bean class="..."> <!-- Resource location is the same as above: --> <constructor-arg value="com.company.module.list" /> </bean>
More information is here.
java.util.Properties
objects to org.springframework.beans.factory.config.PropertiesFactoryBean
which will be merged in given order. Note that under-the-hood below solution uses org.springframework.core.convert.support.StringToPropertiesConverter
which converts from multi-line string to java.util.Properties
.<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"> <util:properties id="configurationProperties"> <prop key="location">/tmp</prop> <prop key="size">1000</prop> </util:properties> <bean id="testConfigurationProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="propertiesArray"> <list> <ref bean="configurationProperties" /> <value> size=10 </value> </list> </property> </bean> </beans>
<bean ...> <property name="url" value="#{ systemProperties[url] ?: 'http://default/url' }" /> </bean>
@Autowired List<ChildBean> list
will work OK both for lists and arrays.autowire="byType"
or autowire="constructor"
e.g. <bean class="ParentBean" autowire="byType" />
(see Autowiring collaborators).
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" p:corePoolSize="5" p:maxPoolSize="30" p:daemon="true" />
MethodInvokingFactoryBean
: <bean id="registerListener1" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject"> <ref local="listenerContainer" /> </property> <property name="targetMethod" value="addListener" /> <property name="arguments"> <list> <ref bean="listener1" /> </list> </property> </bean>
#{listenerContainer.addListener(listener1)}
.
@Autowired
(or normal declarative bean injection via <property>
) will not result the desired effect, as in our singleton bean we want to get new version of prototype bean each time on request. The solutions are:@Autowired ApplicationContext context; ... context.getBean("prototypeBean");
). Drawback: explicit dependency on Spring classes.abstract
(normal protected
method is fairly OK) as it will some difficulties to unit test the bean. Unfortunately, there is no way to configure this functionality using annotations prior to Spring v4.1 (see SPR-5192).<aop:scoped-proxy/>
(see Scoped beans as dependencies). Drawback: does not make sense with prototype
scope as each method invocation will operate with new bean instance which makes it impossible to use this approach with stateful beans.See also:
META-INF/aop.xml
(should be included into WAR or JAR) and e.g. aspect MultiThreadedHttpConnectionManagerAdvice.java
as below: <!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <!-- Add options="-verbose -debug" for more logging --> <weaver> <!-- Only weave classes in these packages: --> <include within="org.apache.commons.httpclient..*" /> <!-- Also include the aspect itself (otherwise "java.lang.NoSuchMethodError: MultiThreadedHttpConnectionManagerAdvice.aspectOf()" will be thrown): --> <include within="aspects.MultiThreadedHttpConnectionManagerAdvice" /> </weaver> <aspects> <!-- weave in just this aspect --> <aspect name="aspects.MultiThreadedHttpConnectionManagerAdvice" /> </aspects> </aspectj>
MultiThreadedHttpConnectionManagerAdvice.java
package aspects; import java.lang.reflect.Field; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.Map; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.util.ReflectionUtils; @Aspect public class MultiThreadedHttpConnectionManagerAdvice { private final Map<Object, ConnectionPoolInfo> infos = new IdentityHashMap<Object, ConnectionPoolInfo>(); /** * Statistics is reset after this amount of invocations. */ private static final int NUMBER_OF_INVOCATIONS_PER_MEASUREMENT = 100; static class ConnectionPoolInfo { public int invocationsNumber; public int maxWaitingThreadsNumber; @Override public String toString() { return "maxWaitingThreadsNumber:" + maxWaitingThreadsNumber; } } /** * @param pool instance of {@link MultiThreadedHttpConnectionManager$ConnectionPool}. */ private void updateStatistics(Object pool) { LinkedList<?> waitingThreads = getField(pool, "waitingThreads"); synchronized (infos) { ConnectionPoolInfo info = infos.get(pool); if (info == null) { info = new ConnectionPoolInfo(); infos.put(pool, info); } if (info.maxWaitingThreadsNumber < waitingThreads.size()) { info.maxWaitingThreadsNumber = waitingThreads.size(); } if (++info.invocationsNumber % NUMBER_OF_INVOCATIONS_PER_MEASUREMENT == 0) { LogFactory.getLog(getClass()).error( "After " + info.invocationsNumber + " invocations " + info + " poolsNumber:" + infos.size()); info.maxWaitingThreadsNumber = 0; } } } @After("execution(* org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.ConnectionPool.createConnection*(..))") public void createConnection(JoinPoint pjp) { updateStatistics(pjp.getThis()); } @Before("execution(* org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.ConnectionPool.getFreeConnection*(..))") public void getFreeConnection(JoinPoint pjp) { updateStatistics(pjp.getThis()); } /** * Get the value of private field {@code fieldName} of given object {@code target}. */ @SuppressWarnings("unchecked") public static <T> T getField(Object target, String fieldName) { Field field = ReflectionUtils.findField(target.getClass(), fieldName); ReflectionUtils.makeAccessible(field); return (T) ReflectionUtils.getField(field, target); } }
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> <context:load-time-weaver /> <aop:aspectj-autoproxy /> </beans> </xml>
java ... -javaagent:/path/to/org.springframework.instrument-3.1.1.RELEASE.jar ...
Troubleshooting:
java.lang.NoSuchMethodError: aspectOf()
is thrown then make sure that aspects are listed in aop.xml
.
PriorityOrdered
for example and to route all calls to this interface to some bean, while all other calls should be routed to original bean.
service.MyServiceImpl
in the example), has to implement some business interface (service.MyService
) which is exposed to other beans in the context.
First write the implementation for the interface that needs to be introduced (if necessary):
package util; import org.springframework.core.PriorityOrdered; /** * Simple implementation of {@link PriorityOrdered} interface that returns the pre-configured order. */ public class FixedPriorityOrder implements PriorityOrdered { private final int order; private FixedPriorityOrder(int order) { this.order = order; } @Override public int getOrder() { return order; } }
Now bringing all together:
<!-- This interceptor will route calls to interfaces that passed bean implements to that bean. --> <bean id="normalPriorityOrder" class="org.springframework.aop.support.DelegatingIntroductionInterceptor"> <constructor-arg> <bean class="util.FixedPriorityOrder"> <constructor-arg value="0" /> </bean> </constructor-arg> </bean> <bean id="descriptorComponent" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <list> <value>org.springframework.beans.factory.InitializingBean</value> <value>org.springframework.core.PriorityOrdered</value> <!-- ... add all other interfaces that target bean implements... --> <value>service.MyService</value> </list> </property> <property name="target"> <bean class="service.MyServiceImpl"> ... </bean> </property> <property name="interceptorNames"> <list> <value>normalPriorityOrder</value> </list> </property> </bean>
@Transactional
support is actually implemented? <tx:annotation-driven … />
in your context file. Element is processed by AnnotationDrivenBeanDefinitionParser
BeanFactoryTransactionAttributeSourceAdvisor
into the context, which implements PointcutAdvisor
interface.getPointcut()
of this advisor returns pre-configured TransactionAttributeSourcePointcut
class instance, that uses AnnotationTransactionAttributeSource
to check if the given method / class should be advised in boolean matches(Method method, Class targetClass)
method.getAdvice()
method of this class returns pre-configured TransactionInterceptor
class instance (which is pre-configured with transaction manager).AbstractAutoProxyCreator#wrapIfNecessary(Object bean, String beanName, Object cacheKey)
(which implements BeanPostProcessor
) is called. It looks up advisors in the context and finally calls AopUtils#findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz)
. If the last one returns the non-empty list, then the target bean is warped into a proxy.JdkDynamicAopProxy#invoke(Object proxy, Method method, Object[] args)
is called. It uses DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, Class targetClass)
to collect all advisors and build the advisor chain.TransactionInterceptor#invoke(final MethodInvocation invocation)
is called, which creates new transaction using the transaction manager.
TransactionTemplate
(see Using the TransactionTemplate) or via PlatformTransactionManager
: @Autowired private PlatformTransactionManager transactionManager; void doInTransaction() { DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); // Do the job transactionManager.commit(transactionStatus); // Error handling is omitted: //transactionManager.rollback(transactionStatus); }
And more exotic:
TransactionProxyFactoryBean
ProxyFactoryBean
and TransactionInterceptor
BeanNameAutoProxyCreator
or a different ...AutoProxyCreator
DataTruncationException
to be thrown in case of specific SQL error codes:package org.mycompany.dao.exception; import org.springframework.dao.DataIntegrityViolationException; public class DataTruncationException extends DataIntegrityViolationException { public DataTruncationException(String msg, Throwable cause) { super(msg, cause); } }
<bean id="exceptionTranslator" class="org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"> <property name="dataSource"> <ref bean="myDataSource" /> </property> <!-- Important that this property is set after datasource was set. Relies on actual SQLErrorCodeSQLExceptionTranslator implementation. --> <property name="sqlErrorCodes.customTranslations"> <array> <!-- Custom mapping of MSSQL (8152) and HSQL (-3401) error code to specific exception for data truncation. --> <bean class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation"> <property name="errorCodes"> <array> <value>8152</value> <value>-3401</value> </array> </property> <property name="exceptionClass" value="org.mycompany.dao.exception.DataTruncationException" /> </bean> </array> </property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="myDataSource" p:exceptionTranslator-ref="exceptionTranslator" />
Later in your Web controller you can for example map given exception to corresponding HTTP code:
import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; ... @ExceptionHandler(DataTruncationException.class) @ResponseStatus(value = HttpStatus.REQUEST_ENTITY_TOO_LARGE) @ResponseBody public String handleDataTruncationException(DataTruncationException ex) { return ExceptionUtils.getStackTrace(ex); }
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/SampleDB"/> <bean id="myDao" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource" /> </bean>
<import resource="classpath:/com/company/module/dao/dao.xml"/> <bean id="testDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:mem:test" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean>
@RunWith(SpringRunner.class) @ContextConfiguration({"classpath:/com/company/module/dao/dao-test.xml"}) public class MyDaoImplTest { @Resource private JdbcTemplate myDao; @Resource private DataSource testDataSource; @Before public void setupJndi() throws NamingException { SimpleNamingContextBuilder.emptyActivatedContextBuilder(); SimpleNamingContextBuilder.getCurrentContextBuilder().bind("java:comp/env/jdbc/SampleDB", testDataSource); } }
CacheException
in my unit test? <bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" lazy-init="true"> <property name="dataSource" ref="myDataSource" /> <property name="mappingResources"> <list> <value>/.../hibernate-mapping.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">...</prop> <prop key="hibernate.cache.use_query_cache">true</prop> <prop key="hibernate.cache.use_second_level_cache">true</prop> <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop> <prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</prop> <prop key="net.sf.ehcache.configurationResourceName">/.../ehcache.xml</prop> </props> </property> </bean>
@ContextConfiguration
: import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @ContextConfiguration("classpath:/.../dao-context.xml") @RunWith(SpringRunner.class) public class MyTest { ... }
Caused by: net.sf.ehcache.CacheException: Another CacheManager with same name 'hibernate-cache' already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following: 1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary 2. Shutdown the earlier cacheManager before creating new one with same name. The source of the existing CacheManager is: URLConfigurationSource [url=file:/C:/workspace/chepo/chepo-dataaccess-db/bin/org/epo/lifesciences/chepo/dao/ehcache.xml] at net.sf.ehcache.CacheManager.assertNoCacheManagerExistsWithSameName(CacheManager.java:457) at net.sf.ehcache.CacheManager.init(CacheManager.java:354) at net.sf.ehcache.CacheManager.<init>(CacheManager.java:242) at net.sf.ehcache.hibernate.EhCacheRegionFactory.start(EhCacheRegionFactory.java:79) ... 59 more at net.sf.ehcache.hibernate.EhCacheRegionFactory.start(EhCacheRegionFactory.java:89) at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:238) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1872) at org.springframework.orm.hibernate3.LocalSessionFactoryBean.newSessionFactory(LocalSessionFactoryBean.java:860) at org.springframework.orm.hibernate3.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:779) at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.afterPropertiesSet(AbstractSessionFactoryBean.java:188) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452) ... 52 more at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:730) at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:196) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1035) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:939) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:485) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:585) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464) at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:103) at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1) at org.springframework.test.context.support.DelegatingSmartContextLoader.loadContext(DelegatingSmartContextLoader.java:228) at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:124) at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:148) ... 24 more
net.sf.ehcache.CacheManager
with name hibernate-cache
is started by some unit test and as CacheManager
shares cache instances via static variable, this cache instance looks like already being initialized for following unit test. In order to solve the issue make sure that:import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @ContextConfiguration("classpath:/.../dao-context.xml") @RunWith(SpringRunner.class) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) public class MyTest { ... }
This will guarantee that Hibernate factory is correctly closed, which in its turn will shutdown cache manager.
destroy-method
: <bean id="cacheManager" class="net.sf.ehcache.CacheManager" destroy-method="shutdown"> <constructor-arg value="classpath:/.../negative-cache.xml" type="java.net.URL" /> </bean>
<%@taglib prefix="s" uri="http://www.springframework.org/tags" %> <s:eval expression="T(org.company.Calculate).getAmount(row.balance)" />
Other examples of SpEL expressions:
#{T(Math).min(T(Runtime).getRuntime().maxMemory() / 3, 0L + T(Integer).MAX_VALUE)}
long
, otherwise “Method call of 'min' is ambiguous, supported type conversions allow multiple variants to match” exception will be thrown.#{T(org.apache.commons.io.IOUtils).toString(new java.net.URL('file:///etc/HOSTNAME'))}
For non-JSP, non-EL approach check Invoke static method from spring config:
<bean id="test" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetClass" value="MyClass" /> <property name="targetMethod" value="staticMethod" /> <property name="arguments"> <list> <value>anArgument</value> </list> </property> </bean>
import org.apache.commons.lang.exception.ExceptionUtils; @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) @ResponseBody public String handleIllegalArgumentException(IllegalArgumentException ex) { return ExceptionUtils.getStackTrace(ex); }
@ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "The passed argument is invalid") public void handleIllegalArgumentException(IllegalArgumentException ex) { }
@ExceptionHandler(IndexOutOfBoundsException.class) public String handleException(IndexOutOfBoundsException ex) { return "pages/errorPage"; }
myService.getInfo()
throws IllegalArgumentException
. In this case Spring will convert returned value from handleIllegalArgumentException()
into application/json, however more appropriate will be text/plain.
To overcome this, exception hander should reset the remembered produced types before returning the response body:
import java.security.Principal; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.HandlerMapping; ... @RequestMapping(value = "/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Info getInfo(Principal principal) { return myService.getInfo(principal); } @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) @ResponseBody public String handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) { resetProducibleMediaTypes(request); return ExceptionUtils.getStackTrace(ex); } /** * Media types to negotiate for error conditions. */ private static final Set<MediaType> DEFAULT_MEDIA_TYPES; static { Set<MediaType> mediaTypes = new HashSet<MediaType>(); Collections.addAll(mediaTypes, MediaType.TEXT_PLAIN, MediaType.ALL); DEFAULT_MEDIA_TYPES = Collections.unmodifiableSet(mediaTypes); } /** * Content types, which have been remembered to handle the return type of handler method, need to be reset to allow * the return type of exception handler to have different content type. */ private static void resetProducibleMediaTypes(HttpServletRequest request) { request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, DEFAULT_MEDIA_TYPES); }
<mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" /> </mvc:interceptors>
DispatcherServlet
) binds the HTTP request to current thread, which makes it inaccessible for spawned threads. To overcome the problem the following conditions should be met:threadContextInheritable
property of DispatcherServlet
or RequestContextFilter
should be set to true, for example: <servlet> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>threadContextInheritable</param-name> <param-value>true</param-value> </init-param> </servlet>
SimpleAsyncTaskExecutor
) should be used. That means: ThreadPoolTaskExecutor
or WorkManagerTaskExecutor
will not work.
ModelAndView
for that (from Can I return two models in ModelAndView in Spring MVC):@RequestMapping(value = {"/registrationForm"}, method = RequestMethod.GET) public ModelAndView newForm() { final ModelAndView modelAndView = new ModelAndView("registrationForm"); modelAndView.addObject("registrationForm", new RegistrationForm()); modelAndView.addObject("sharedData", sharedData); return modelAndView; }
javax.validation:validation-api
into you dependencies list.@Valid
: @RequestMapping(method = RequestMethod.POST) public String onFormSubmitted(@ModelAttribute("myForm") @Valid MyForm myForm, BindingResult result) { if (result.hasErrors()) { return "myForm"; } return "formSubmitted"; }
@InitBinder protected void initBinder(WebDataBinder binder) { binder.setValidator(new MyFormValidator()); }
import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; public class MyFormValidator implements Validator { /** * @see org.springframework.validation.Validator#supports(java.lang.Class) */ public boolean supports(Class<?> clazz) { return MyForm.class.isAssignableFrom(clazz); } /** * @see org.springframework.validation.Validator#validate(java.lang.Object, org.springframework.validation.Errors) */ public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "required", "Field is required."); } }
Additional info:
<dependency> <groupId>net.tanesha.recaptcha4j</groupId> <artifactId>recaptcha4j</artifactId> <version>0.0.7</version> </dependency>
Use of a “*” for the field name is important, otherwise MVC will try to resolve the field value:
<%@ page import="net.tanesha.recaptcha.ReCaptcha" %> <%@ page import="net.tanesha.recaptcha.ReCaptchaFactory" %> <%@ page import="com.company.module.web.SharedData" %> ... <form:form method="post" commandName="registrationForm"> <form:errors path="captcha*" /><br/> <% SharedData sharedData = (SharedData) request.getAttribute("sharedData"); ReCaptcha captcha = ReCaptchaFactory.newReCaptcha(sharedData.getRecaptchaPublicKey(), sharedData.getRecaptchaPrivateKey(), false); out.print(captcha.createRecaptchaHtml(null, null)); %> </form:form>
The controller code:
import net.tanesha.recaptcha.ReCaptchaImpl; import net.tanesha.recaptcha.ReCaptchaResponse; @Controller @SessionAttributes("sharedData") public class RegistrationFormController { @Autowired private SharedData sharedData; @RequestMapping(value = {"/registrationForm"}, method = RequestMethod.GET) public ModelAndView newForm() { final ModelAndView modelAndView = new ModelAndView("registrationForm"); modelAndView.addObject("registrationForm", new RegistrationForm()); modelAndView.addObject("sharedData", sharedData); return modelAndView; } @RequestMapping(value = {"/registrationForm"}, method = RequestMethod.POST) public String onRegistrationFormSubmitted(HttpServletRequest request, @RequestParam("recaptcha_challenge_field") String challenge, @RequestParam("recaptcha_response_field") String response, @ModelAttribute("registrationForm") @Valid RegistrationForm registrationForm, BindingResult result) { final ReCaptchaImpl recaptcha = new ReCaptchaImpl(); recaptcha.setPrivateKey(sharedData.getRecaptchaPrivateKey()); final ReCaptchaResponse captchaResponse = recaptcha.checkAnswer(request.getRemoteAddr(), challenge, response); if (!captchaResponse.isValid()) { // 2nd argument should match <form:errors path="..."> in JSP: result.addError(new FieldError("comment", "captcha", "Captcha is wrong")); } if (result.hasErrors()) { return "registrationForm"; } // Perform business logic, e.g. persist registration data return "formSubmitted"; } }
And shared data is initialized in Spring context:
<bean class="com.company.module.web.SharedData"> <!-- ReCaptcha private key --> <constructor-arg value="5LemXMassAAsAJ9itNONm2hYjWmvAsaRBxyIWYfk" /> <!-- ReCaptcha public key --> <constructor-arg value="5LemcnmaAAssaBJcF6gYH-zcBTeJ_ArWakbg3wsX" /> </bean>
Check also Integrating Captcha with Spring Security, CAPTCHA in java.
index.html
) be handled via Spring and not your web container? <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.html</url-pattern> <url-pattern>/index.html</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
To map certain resources back to default servlet, use the following:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping>
Mind the priority of URL-matching rules1):
/*
)*.html
)/
)"Девица не хочет лезть в Окно" – device not compatible with Windows.