关于SpringMVC事务失效的分析

来源:互联网 发布:淘宝应用开发者平台 编辑:程序博客网 时间:2024/04/28 02:59
看文章的朋友,假定你已经很清楚事务是什么,并且相信自己配正确了事务。并且期望使用最简单的方法来使用Spring的增强事务。

1、首先下面三个标签必须弄清楚使用的含义,从官网看完后总结如下:
(1) <context:component-scan>扫描和@Component相关的注解。其中包括@Controller,@Service,@Repository等,这些都是从@Component中继承而来的;并且完成对应注解内容的注入,也就是说这个标签它涵盖了下面这个标签的功能。
(2)<context:annotation-config> 扫描和@Autowired相关、相似的注解。其中包括@Required,@Autowired等等,这些标签有些是JSR规范的,所以Java原生的辅助注解。
(3)<mvn:annotation-driven>扫描Java配置MVC的部分,官网的内容指示的是:@EnableWebMvc,@Configuration修饰的一个类,等同于编程式声明。然后扫这个类就知道了mvc的配置,如果用了xml根本不需要这样弄。
这里强调一下如果使用SpringMVC,就用。因为Ioc是顶端依赖驱动的。也就是说,扫描@Controller是顶端,一直往下找@Autowried。如果没有@Controller,就找@Service作为顶端,一直网下载@Autowried。如果没有使用SpringMVC,那么就用。最典型的实践就是Struts2里面用struts.xml配置了URL的转发,然后里面的属性就用@Autowried,这样annotation-config就可以自动帮你注入了。

2、理解SpringMVC和Spring的关系:
无可厚非,无论是Spring也好,SpringMVC也好,都是延续一贯强大的Bean管理思想:Ioc。因此,从官网介绍DispatcherServlet的内容中可以得知,SpringMVC是一个容器。只不过这个容器是用来管理MVC组合模块的。而且这个容器是Spring父容器的一个子容器。也就是说,SpringMVC其实可以看成是一个盒子,这个盒子放在Spring这个大盒子里面。从层级而言,SpringMVC和Mybatis、Hibernate、Quartz等等基于Spring注入管理的技术框架,都是同一等级的。

3、SpringMVC和Spring的加载:
(1)对于Spring和SpringMVC而言,到底谁先加载是关键。两者都是各自维护自己的一个容器。但是SpringMVC的容器要利用一个负责分发请求的DispatcherServlet。而Spring的IOC容器是利用Listern对每个构建的Bean(包括Servlet)都进行生命周期的监听。所以,Spring要快于SpringMVC被加载。
(2)既然这样,首先让Spring加载,利用SpringAOP,把符合PointCut的对象都做动态代理;接着再启动SpringMVC,加载的同时,把动态代理好的@Service对象都@Autowired到相关的对象中。

4、实验标签:
(1)
#在springmvc的xml中(DispatcherServlet,子容器)配置:
<context:component-scanbase-package="org.gdut.ptg"/>
#在spring的xml中(父容器)配置:
<context:component-scanbase-package="org.gdut.ptg">
<context:exclude-filterexpression="org.springframework.stereotype.Controller"type="annotation" />
</context:component-scan>
#(在某个@Service类的构造器中打印一下)结果:实例化两次
org.gdut.ptg.core.lowsafe.business.LowsafeApplicationBusiness
LowsafeApplicationBusiness 实例化
org.gdut.ptg.core.lowsafe.business.LowsafeApplicationBusiness$$EnhancerByCGLIB$$d8b5b8fa
LowsafeApplicationBusiness 实例化
2014-01-11 20:50:35.769:INFO:/:Initializing SpringFrameworkServlet 'springMVC'
org.gdut.ptg.core.lowsafe.business.LowsafeApplicationBusiness
LowsafeApplicationBusiness 实例化

(2)
#在springmvc的xml中(DispatcherServlet,子容器)配置:
不配置扫描
#在spring的xml中(父容器)配置:
<context:annotation-config/>
#(在某个@Service类的构造器中打印一下)结果:没有任何显示,显然没有被实例化

(3)
#在springmvc的xml中(DispatcherServlet,子容器)配置:
<context:component-scanbase-package="org.gdut.ptg"use-default-filters="false">
<context:include-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
#在spring的xml中(父容器)配置:
<context:component-scanbase-package="org.gdut.ptg">
<context:exclude-filterexpression="org.springframework.stereotype.Controller"type="annotation" />
</context:component-scan>
#(在某个@Service类的构造器中打印一下)结果:报错,说@Controller找不到对应的@Autowried的@Service类
org.gdut.ptg.core.lowsafe.business.LowsafeApplicationBusiness
LowsafeApplicationBusiness 实例化
org.gdut.ptg.core.lowsafe.business.LowsafeApplicationBusiness$$EnhancerByCGLIB$$d8b5b8fa
LowsafeApplicationBusiness 实例化
Caused by: 
org.springframework.beans.factory.BeanCreationException: Couldnot autowire field: privateorg.gdut.ptg.core.fivesafe.business.FivesafeStandardBusinessorg.gdut.ptg.core.fivesafe.action.FivesafeStandardAction.business;nested exception isorg.springframework.beans.factory.NoSuchBeanDefinitionException: Noqualifying bean of type[org.gdut.ptg.core.fivesafe.business.FivesafeStandardBusiness]found for dependency: expected at least 1 bean which qualifies asautowire candidate for this dependency. Dependency annotations:{@org.springframework.beans.factory.annotation.Autowired(required=true)}

5、测试总结:
上面的第(3)步配置,绝对是我想要的结果。同时第(2)步说明了在实际的操作中意义不大了,因为文档里面表述的很清楚。通过来扫描几个关键的标签,并完成注入。
但是为什么第(3)步会出错?
(1)首先Spring不可能管理的了SpringMVC这个容器里面的Controller类(第一个原因是,父容器不可能涉及并管理子容器的内容,第二个原因是,父容器先于子容器构建,没有任何程序可以操纵未来的类,除非用监听,不过过显然SpringMVC的设计没必要如此复杂);不过SpringMVC可以使用父容器里面和自己同层次的类,也就是SpringMVC是可以使用Mybatis或者Hibernate的作为自己的Service或者Repository。
(2)然后我们看到上面第(3)步实验打印的内容可以看到,其实被事务增强以后的类的类型已经变了(CGLIB字节码增强)。如果类型变了,那当然是找不到。因为Spring的注入分两种:byName和byType。@Autowired属于byType型的标签。可以通过@Qualifier来转成byName,或者使用JSR的@Resource(name=xxxx)。不过问题来了,CGLIB代理后的类型在Spring的容器里面使用的名字就是类的名字,也就是说,你在用@Bean("")给个名字给这个Service也没有用过。
(3)最后回顾一下官网对byName注入的建议。可以发现,使用byName主要的目的是为了解决同类型不同子类区分注入而使用的。换句话受,byType注入才是Spring注入的推荐用法。那么要byType,就可以通过接口或者父类的方法提高一个层次,这样,通过SpringAOP代理出来的增强事务类,就可以被@Autowried标签所识别并注入。
(4)我看了一下代码,发现自己的Service层没有用接口。其实是因为大家都知道要实现增强事务,一种手段是用CGLIB基于class文件,从字节码级别的增强,其实是一个钩子,必须要实实在在的配置byName才可以注入到目标中。另外一种手段是基于接口,Proxy通过代理一个实现类,并且把为增强目标的接口实现化,从而实现增强的目的。
最后,我为每一个Service层的类都写了接口,然后Controller层的类注入的时候,用的是接口类型,这样子,事务就成功了。

6、番外话:
如果你想基于CGLIB,然后又想用SpringMVC。那么Service层的就不要用注解,都用xml配进去。然后在Controller层通过byName注入,那么也可以。不过如果用了SpringMVC,是傻瓜都知道想利用注解的轻量。那么就老老实实的为Service层(总之就是你要做事务的那一层,Service层只是一个定义而已。关键是哪一层要事务,就那一层弄)写接口吧。

原博客地址:http://blog.sina.com.cn/s/blog_711ee9600101n9ds.html
0 0
原创粉丝点击