Spring-MVC : Creating a good infrastructure(configuration) and avoiding duplicates

前端 未结 2 1156
悲&欢浪女
悲&欢浪女 2020-12-20 09:18

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

相关标签:
2条回答
  • 2020-12-20 10:02

    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 @Components. 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 @Controllers and what is loaded by the DispatcherServlet just @Controllers 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 ;)...

    0 讨论(0)
  • 2020-12-20 10:17

    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

    0 讨论(0)
提交回复
热议问题