Spring MVC @Controller:由于“component-scan”标签的“use-default-filter”导致的事务性问题

时间:2015-11-14 13:30:48

标签: java spring spring-mvc spring-transactions

我是Spring MVC的新手,所以我正在创建一个小型的Web应用程序,只是为了尝试它。

我正在使用Spring 4.2和Hibernate 5.

网络应用有一个Spring servlet-context.xml和一个Spring application-context.xml

我有@Controller的方法,可以使用@Service。此服务的findAll()方法标记为@Transactional

web.xml看起来像这样:

...
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:*-context.xml
    </param-value>
</context-param>

<servlet>
    <servlet-name>myDispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>myDispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
...

控制器:

@Controller
public class TestOneController
{
    @Autowired
    private UserManager userManager;

    @RequestMapping("views/test")
    public ModelAndView defaultResolution()
    {
        List<User> users = userManager.findAll();
        // here I build my String msg object
        return new ModelAndView("views/resolution", "msg", msg);
    }
}

服务:

@Service("userManager")
public class UserManagerImpl implements UserManager
{
    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public List<User> findAll()
    {
        return userDao.findAll();
    }
}

最后服务使用的Dao:

@Repository("userDao")
public class UserDaoImpl implements UserDao
{

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<User> findAll()
    {
        Query query = sessionFactory.getCurrentSession().createQuery("from User ");
        return query.list();
    }
}

我的问题: 我已经尝试了几种配置,我认为这些配置是等效的,但是当我从页面中的链接点击映射的Controller方法时,我得到了一个org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread。有人可以解释一下,为什么在我使用配置2时会引发此异常,而不是在配置1和3上引发异常?

配置1(正常工作)

应用context.xml中

...
<tx:annotation-driven />
<context:component-scan base-package="my.package">
    <context:exclude-filter type="annotation"
        expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<bean id="transactionManager"
    class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:hibernate.cfg.xml" />
    <property name="packagesToScan">
        <list>
            <value>my.package.entities</value>
        </list>
    </property>
</bean>

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myDb" />
</bean>
...

servlet的context.xml中

...
<context:component-scan base-package="my.package.inner.package">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
        value="org.springframework.web.servlet.view.JstlView" />
    <property name="prefix" value="/" />
    <property name="suffix" value=".jsp" />
</bean>
...

配置2(引发异常) 与1相同的配置,与<context:component-scan>的{​​{1}}标记不同,它具有更接近根的基础包:

servlet-context.xml

请注意,这与<context:component-scan base-package="my.package"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> 中的组件扫描的基本包相同。

配置3(正常工作) 与2相同(因此在application-context.xml中使用base-package="my.package"),区别在于我没有使用servlet-context.xml注释我的服务,而是在@Service中将其明确声明为bean :

application-context.xml

TL; DR 有人可以解释一下,当我用配置2而不是配置1和3命中Controller映射方法时,为什么我得到... <bean id="userManager" class="my.package.services.impl.UserManagerImpl"> </bean> ... ? 我相信所有这三种配置都是等效的。

谢谢。

1 个答案:

答案 0 :(得分:0)

经过几个小时的反复试验,我发现了问题。

在所有3种配置中,component-scan的{​​{1}}标记需要另一个属性:servlet-context.xml必须设置为“false”。

use-default-filters

如果将其设置为“true”(默认情况下),则基本上就像拥有这样的默认包含过滤器:

<context:component-scan base-package="my.package" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

这意味着在包扫描期间,它还加载了定义的<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> (和@Service等)。 由于服务是由Servlet上下文加载的,没有@Repository标记,因此没有为服务启用事务上下文,并且引发了异常。

在配置1中,由于用于扫描的基本包受到限制(仅包括Controller包),因此绕过了该问题(Servlet上下文仅加载了Controller,因此使用的服务是应用程序上下文加载的服务,已启用事务上下文。

在配置3中,由于我在右侧“事务”上下文(<tx:annotation-driven />)中使用了Service bean的“手动定义”,因此也绕过了问题。