I am working on a Spring-MVC application since some time. Recently I ran into some problems with @Scheduled methods, and I noticed that the whole configuration is getting lo
There are a lot of things that can be improved with your code and configuration. Lets start with your dao, don't store the Session
in a instance variable and I strongly suggest to use constructor injection for the required dependencies. Taking this into account your dao(s) should look something like this.
@Transactional
@Repository
public class CanvasDAOImpl implements CanvasDAO{
private final SessionFactory sessionFactory;
@Autowired
public CanvasDAOImpl(SessionFactory sessionFactory) {
this.sessionFactory=sessionFactory;
}
@overrride
public returnType methodName(params..){
Session session = this.sessionFactory.getCurrentSession();
// Do stuff with the session.
}
}
No more setters (especially not for the Session
!) just a plain class. The same applies for the @Service
classes.
@Service
@Transactional
public class CanvasServiceImpl implements CanvasService {
private final CanvasDAO canvasDAO;
public CanvasServiceImpl(CanvasDAO canvasDAO) {
this.canvasDAO=canvasDAO;
}
//methods
}
In your configuration you have explicitly defined all your @Repository
and @Service
beans. You also have a <context:component-scan />
which already detects all @Component
s. Remove all explicitly declared @Repository
and @Service
beans. This will really clean up your configuration!.
In your hibernate configuration the c3p0
and connection
properties are useless as you are injecting a DataSource
and hibernate is't managing it but Spring is. Remove those lines. Also to cleanup this configuration further instead of specifying each and every class it needs to proces add packagesToScan
so it will automatically detect @Entity
annotated beans.
<context:annotation-config />
is already implied by the use of <context:component-scan />
so you can remove it as it basically duplicates things.
You have explicitly defined a RequestMappingHandlerMapping
which doesn't do anything as there is already one registered by <mvc:annotation-driven />
. It only takes up memory, the MappingJackson2HttpMessageConverter
is registered automatically of Jackson2 is detected on the class path so no need to do that.
Chancing the Locale
doesn't work for all URLs as you forgot to register the interceptor with the <mvc:annotation-driven />
element.
When applying all that advice to your classes and configuration the remaining configuration looks like this.
<context:component-scan base-package="com.journaldev.spring"/>
<context:property-placeholder location="classpath:application.properties"/>
<mvc:annotation-driven>
<mvc:argument-resolvers>
<beans:bean class="org.springframework.mobile.device.DeviceWebArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:interceptors>
<beans:bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor"/>
<beans:ref bean="localeChangeInterceptor" />
</mvc:interceptors>
<mvc:default-servlet-handler/>
<resources mapping="/resources/" location="/resources/"/>
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/"/>
<beans:property name="suffix" value=".jsp"/>
</beans:bean>
<beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<beans:property name="driverClassName" value="org.postgresql.Driver"/>
<beans:property name="url"
value="jdbc:postgresql://localhost:5432/dbname"/>
<beans:property name="username" value="dbuser"/>
<beans:property name="password" value="dbpass"/>
<beans:property name="removeAbandoned" value="true"/>
<beans:property name="removeAbandonedTimeout" value="20"/>
<beans:property name="defaultAutoCommit" value="false"/>
</beans:bean>
<!-- Hibernate 4 SessionFactory Bean definition -->
<beans:bean id="hibernate4AnnotatedSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="packagesToScan" value="com.journaldev.spring.model" />
</beans:property>
<beans:property name="hibernateProperties">
<beans:props>
<beans:prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</beans:prop>
<beans:prop key="hibernate.show_sql">false</beans:prop>
<!-- <beans:prop key="hibernate.jdbc.batch_size">1000</beans:prop>
<beans:prop key="hibernate.order_updates">true</beans:prop>-->
<beans:prop key="hibernate.hbm2ddl.auto">update</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
<task:annotation-driven/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<beans:bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<beans:property name="sessionFactory" ref="hibernate4AnnotatedSessionFactory"/>
</beans:bean>
<!-- <beans:bean id="bayeux" class="org.cometd.server.BayeuxServerImpl" init-method="start" destroy-method="stop">
<beans:property name="transports">
<beans:list>
<beans:bean id="jsonTransport" class="org.cometd.server.transport.JSONTransport">
<beans:constructor-arg ref="bayeux"/>
</beans:bean>
<beans:bean id="jsonpTransport" class="org.cometd.server.transport.JSONPTransport">
<beans:constructor-arg ref="bayeux"/>
</beans:bean>
</beans:list>
</beans:property>
</beans:bean>-->
<!-- locale -->
<beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="classpath:/locale/messages"/>
<beans:property name="defaultEncoding" value="UTF-8"/>
</beans:bean>
<!-- default locale -->
<beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<beans:property name="defaultLocale" value="de"/>
</beans:bean>
<!-- Change locale via url. -->
<beans:bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<beans:property name="paramName" value="lang"/>
</beans:bean>
<beans:bean id="handlerMapping" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<beans:property name="interceptors">
<beans:list>
<beans:ref bean="localeChangeInterceptor"/>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean class="com.journaldev.spring.service.DoNotTruncateMyUrls"/>
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="52428800"/>
</beans:bean>
</beans:beans>
However you really should split things up in what is loaded by the ContextLoaderListener
basically everything BUT @Controller
s and what is loaded by the DispatcherServlet
just @Controller
s and web related beans.
So the servlet-context.xml
should look something like this.
<context:component-scan base-package="com.journaldev.spring" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<context:property-placeholder location="classpath:application.properties"/>
<mvc:annotation-driven>
<mvc:argument-resolvers>
<beans:bean class="org.springframework.mobile.device.DeviceWebArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:interceptors>
<beans:bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor"/>
<beans:ref bean="localeChangeInterceptor" />
</mvc:interceptors>
<mvc:default-servlet-handler/>
<resources mapping="/resources/" location="/resources/"/>
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/"/>
<beans:property name="suffix" value=".jsp"/>
</beans:bean>
<!-- locale -->
<beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="classpath:/locale/messages"/>
<beans:property name="defaultEncoding" value="UTF-8"/>
</beans:bean>
<!-- default locale -->
<beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<beans:property name="defaultLocale" value="de"/>
</beans:bean>
<!-- Change locale via url. -->
<beans:bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<beans:property name="paramName" value="lang"/>
</beans:bean>
<beans:bean id="handlerMapping" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
<beans:property name="interceptors">
<beans:list>
<beans:ref bean="localeChangeInterceptor"/>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean class="com.journaldev.spring.service.DoNotTruncateMyUrls"/>
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="52428800"/>
</beans:bean>
Then add what was removed and a <context:component-scan />
(or modify it) to the root-context.xml
.
<context:component-scan base-package="com.journaldev.spring">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<context:property-placeholder location="classpath:application.properties"/>
<beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<beans:property name="driverClassName" value="org.postgresql.Driver"/>
<beans:property name="url"
value="jdbc:postgresql://localhost:5432/dbname"/>
<beans:property name="username" value="dbuser"/>
<beans:property name="password" value="dbpass"/>
<beans:property name="removeAbandoned" value="true"/>
<beans:property name="removeAbandonedTimeout" value="20"/>
<beans:property name="defaultAutoCommit" value="false"/>
</beans:bean>
<!-- Hibernate 4 SessionFactory Bean definition -->
<beans:bean id="hibernate4AnnotatedSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="packagesToScan" value="com.journaldev.spring.model" />
</beans:property>
<beans:property name="hibernateProperties">
<beans:props>
<beans:prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</beans:prop>
<beans:prop key="hibernate.show_sql">false</beans:prop>
<!-- <beans:prop key="hibernate.jdbc.batch_size">1000</beans:prop>
<beans:prop key="hibernate.order_updates">true</beans:prop>-->
<beans:prop key="hibernate.hbm2ddl.auto">update</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
<task:annotation-driven/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<beans:bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<beans:property name="sessionFactory" ref="hibernate4AnnotatedSessionFactory"/>
</beans:bean>
<!-- <beans:bean id="bayeux" class="org.cometd.server.BayeuxServerImpl" init-method="start" destroy-method="stop">
<beans:property name="transports">
<beans:list>
<beans:bean id="jsonTransport" class="org.cometd.server.transport.JSONTransport">
<beans:constructor-arg ref="bayeux"/>
</beans:bean>
<beans:bean id="jsonpTransport" class="org.cometd.server.transport.JSONPTransport">
<beans:constructor-arg ref="bayeux"/>
</beans:bean>
</beans:list>
</beans:property>
</beans:bean>-->
Remove the <import resource="servlet-context.xml" />
from your security-context.xml
.
Finally let the DispatcherServlet
load the servlet-context.xml
instead of the security-context.xml
.
Now as you aren't loading beans twice per context anymore you should have a reduced memory footprint and you shouldn't have 4 scheduled jobs any more. Also your Email
class should, ideally, also be a Spring managed bean and I would suggest using the JavaMailSender
API which simplifies sending emails.
So basically the task ahead for you is to mainly remove things, you will end up with less code and less configuration and still achieve the same.
For more hands-on-advice I'm for hire ;)...
The point of using annotations is to remove the need for xml configuration. If you are using the Spring @Service
and @Repository
annotations then you can remove all the service and dao definitions from the xml posted above and replace them with with one line.
<context:component-scan base-package="x.y.z.service, x.y.z.repository" />
You can then update all your service classes to be like below using the @Autowired
annotation to have Spring inject the relevant DAO:
@Service
@Transactional
public class CanvasServiceImpl implements CanvasService {
@Autowired
private CanvasDAO canvasDAO;
}
You also load the Spring security context multiple times in your web.xml. Once via The ContextLoaderListener and once via the RequestDispatcher configuration:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml,/WEB-INF/spring/appServlet/security-applicationContext.xml</param-value>
</context-param>
<session-config>
<session-timeout>1440</session-timeout>
</session-config>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/security-applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
</web-app>
And then, just to finish things off you also import the web application configuration in the security context each time it is loaded.
<import resource="servlet-context.xml" />
I would normally configure as outlined here. I believe this is considered best practice. Essentially you have the Dispatcher Servlet initialize only the web related stuff. You have the Listener initialize the non-web stuff.
http://simone-folino.blogspot.co.uk/2012/05/dispatcherservlet-vs.html