应用分层的迷惑
Web、Service及DAO三层划分就像西方国家的立法、行政、司法三权分立一样被奉为金科玉律,甚至有开发人员认为如果要使用Spring事务管理就一定先要进行三层的划分.这个看似荒唐的论调在开发人员中颇有市场.更有甚者,认为每层先定义一个接口,然后再定义一个实现类.其结果是:一个很简单的功能,也至少需要3个接口,3个类,再加上视图层的JSP和JS等,打牌都可以转上两桌了,这种误解贻害不浅.
对将“面向接口编程”奉为圭臬,认为放之四海而皆准的论调,笔者深不以为然.是的,“面向接口编程”是MartinFowler,RodJohnson这些大师提倡的行事原则.如果拿这条原则去开发架构,开发产品,怎么强调都不为过.但是,对于我们一般的开发人员来说,做的最多的是普通工程项目,往往最多的只是一些对数据库增、删、查、改的功能.此时,“面向接口编程”除了带来更多的类文件外,看不到更多其它的好处.
Spring框架提供的所有附加的好处(AOP、注解增强、注解MVC等)唯一的前提就是让POJO的类变成一个受Spring容器管理的Bean,除此以外没有其它任何的要求.下面的实例用一个POJO完成所有的功能,既是Controller,又是Service,还是DAO:
清单5.MixLayerUserService.java
62.packageuser.mixlayer;
63.importorg.springframework.beans.factory.annotation.Autowired;
64.importorg.springframework.jdbc.core.JdbcTemplate;
65.importorg.springframework.stereotype.Controller;
66.importorg.springframework.web.bind.annotation.RequestMapping;
67.//①.将POJO类通过注解变成SpringMVC的Controller
68.@Controller
69.publicclassMixLayerUserService{
70.
71.//②.自动注入JdbcTemplate
72.@Autowired
73.privateJdbcTemplatejdbcTemplate;
74.
75.//③.通过SpringMVC注解映URL请求
76.@RequestMapping("/logon.do")
77.publicStringlogon(StringuserName,Stringpassword){
78.if(isRightUser(userName,password)){
79.Stringsql="UPDATEt_useruSETu.score=u.score ?WHEREuser_name=?";
80.jdbcTemplate.update(sql,20,userName);
81.return"success";
82.}else{
83.return"fail";
84.}
85.}
86.privatebooleanisRightUser(StringuserName,Stringpassword){
87.//dosth...
88.returntrue;
89.}
90.}
通过@Controller注解将MixLayerUserService变成Web层的Controller,同时也是Service层的服务类.此外,直接使用JdbcTemplate访问数据,MixLayerUserService还是一个DAO.来看一下对应的Spring配置文件:
清单6.applicationContext.xml
91.<?xmlversionxmlversion="1.0"encoding="UTF-8"?>
92.<beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans"
93.xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
94.xmlns:context="http://www.springframework.org/schema/context"
95.xmlns:p="http://www.springframework.org/schema/p"
96.xmlns:aop="http://www.springframework.org/schema/aop"
97.xmlns:tx="http://www.springframework.org/schema/tx"
98.xsi:schemaLocation="http://www.springframework.org/schema/beans
99.http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
100.http://www.springframework.org/schema/context
101. http://www.springframework.org/schema/context/spring-context-3.0.xsd
102. http://www.springframework.org/schema/aop
103. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
104. http://www.springframework.org/schema/tx
105.http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
106.
107.<context:component-scanbase-packagecontext:component-scanbase-package="user.mixlayer"/>
108.
109.<beanclassbeanclass="org.springframework.web.servlet.mvc.annotation
110. .AnnotationMethodHandlerAdapter"/>
111.
112.
113.<beanclassbeanclass="org.springframework.web.servlet.view
114. .InternalResourceViewResolver"
115.pp:prefix="/WEB-INF/jsp/"p:suffix=".jsp"/>
116.
117.
118.<beanidbeanid="dataSource"
119.class="org.apache.commons.dbcp.BasicDataSource"
120.destroy-method="close"
121.p:driverClassName="oracle.jdbc.driver.OracleDriver"
122.p:url="jdbc:oracle:thin:@localhost:1521:orcl"
123.p:username="test"
124.p:password="test"/>
125.
126.<beanidbeanid="jdbcTemplate"
127.class="org.springframework.jdbc.core.JdbcTemplate"
128.p:dataSource-ref="dataSource"/>
129.
130.
131.<beanidbeanid="jdbcManager"
132.class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
133.p:dataSource-ref="dataSource"/>
134.
135.
136.<aop:configproxy-target-classaop:configproxy-target-class="true">
137.<aop:pointcutidaop:pointcutid="serviceJdbcMethod"
138.expression="execution(public*user.mixlayer.MixLayerUserService.*(..))"/>
139.<aop:advisorpointcut-refaop:advisorpointcut-ref="serviceJdbcMethod"
140.advice-ref="jdbcAdvice"order="0"/>
141.</< span>aop:config>
142.<tx:adviceidtx:adviceid="jdbcAdvice"transaction-manager="jdbcManager">
143.<tx:attributes>
144.<tx:methodnametx:methodname="*"/>
145.</< span>tx:attributes>
146.</< span>tx:advice>
147.</< span>beans>
在①处,我们定义配置了AnnotationMethodHandlerAdapter,以便启用SpringMVC的注解驱动功能.而②和③处通过Spring的aop及tx命名空间,以及Aspject的切点表达式语法进行事务增强的定义,对MixLayerUserService的所有公有方法进行事务增强.要使程序能够运行起来还进行web.xml的相关配置:
清单7.web.xml
1.<?xmlversionxmlversion="1.0"encoding="GB2312"?>
2.<web-appversionweb-appversion="2.4"xmlns="http://java.sun.com/xml/ns/j2ee"
3.xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4.xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
5.http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
6.<context-param>
7.<param-name>contextConfigLocation</< span>param-name>
8.<param-value>classpath*:user/mixlayer/applicationContext.xml</< span>param-value>
9.</< span>context-param>
10.<context-param>
11.<param-name>log4jConfigLocation</< span>param-name>
12.<param-value>/WEB-INF/classes/log4j.properties</< span>param-value>
13.</< span>context-param>
14.
15.<listener>
16.<listener-class>
17.org.springframework.web.util.Log4jConfigListener
18.</< span>listener-class>
19.</< span>listener>
20.<listener>
21.<listener-class>
22.org.springframework.web.context.ContextLoaderListener
23.</< span>listener-class>
24.</< span>listener>
25.
26.<servlet>
27.<servlet-name>user</< span>servlet-name>
28.<servlet-class>
29.org.springframework.web.servlet.DispatcherServlet
30.</< span>servlet-class>
31.
32.<init-param>
33.<param-name>contextConfigLocation</< span>param-name>
34.<param-value>classpath:user/mixlayer/applicationContext.xml</< span>param-value>
35.</< span>init-param>
36.<load-on-startup>1</< span>load-on-startup>
37.</< span>servlet>
38.<servlet-mapping>
39.<servlet-name>user</< span>servlet-name>
40.<url-pattern>*.do</< span>url-pattern>
41.</< span>servlet-mapping>
42.</< span>web-app>
这个配置文件很简单,唯一需要注意的是DispatcherServlet的配置.默认情况下SpringMVC根据Servlet的名字查找WEB-INF下的-servlet.xml作为SpringMVC的配置文件,在此,我们通过contextConfigLocation参数显式指定SpringMVC配置文件的确切位置.
将org.springframework.jdbc及org.springframework.transaction的日志级别设置为DEBUG,启动项目,并访问http://localhost:8088/logon.do?userName=tom应用,MixLayerUserService#logon方法将作出响应,查看后台输出日志:
清单8执行日志
43.13:24:22,625DEBUG(AbstractPlatformTransactionManager.java:365)-
44.Creatingnewtransactionwithname
45. [user.mixlayer.MixLayerUserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT
46.13:24:22,906DEBUG(DataSourceTransactionManager.java:205)-
47.AcquiredConnection[org.apache.commons.dbcp.PoolableConnection@6e1cbf]
48. forJDBCtransaction
49.13:24:22,921DEBUG(DataSourceTransactionManager.java:222)-
50.SwitchingJDBCConnection
51. [org.apache.commons.dbcp.PoolableConnection@6e1cbf]tomanualcommit
52.13:24:22,921DEBUG(JdbcTemplate.java:785)-
53.ExecutingpreparedSQLupdate
54.13:24:22,921DEBUG(JdbcTemplate.java:569)-
55.ExecutingpreparedSQLstatement
56. [UPDATEt_useruSETu.score=u.score ?WHEREuser_name=?]
57.13:24:23,140DEBUG(JdbcTemplate.java:794)-
58.SQLupdateaffected0rows
59.13:24:23,140DEBUG(AbstractPlatformTransactionManager.java:752)-
60.Initiatingtransactioncommit
61.13:24:23,140DEBUG(DataSourceTransactionManager.java:265)-
62.CommittingJDBCtransactiononConnection
63. [org.apache.commons.dbcp.PoolableConnection@6e1cbf]
64.13:24:23,140DEBUG(DataSourceTransactionManager.java:323)-
65.ReleasingJDBCConnection[org.apache.commons.dbcp.PoolableConnection@6e1cbf]
66. aftertransaction
67.13:24:23,156DEBUG(DataSourceUtils.java:312)-
68.ReturningJDBCConnectiontoDataSource
日志中粗体部分说明了MixLayerUserService#logon方法已经正确运行在事务上下文中.Spring框架本身不应该是复杂化代码的理由,使用Spring的开发者应该是无拘无束的:从实际应用出发,去除掉那些所谓原则性的接口,去除掉强制分层的束缚,简单才是硬道理.