SSM(Spring+SpringMVC+Mybatis)框架配置例子
首先從web.xml開始:
<?xmlversion="1.0"encoding="UTF-8"?>
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!—讀取spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- spring的監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!—session創建/銷燬監聽器 -->
<listener>
<listener-class>com.org.xcs.listener.SessionListener</listener-class>
</listener>
<!—字符集過濾器 -->
<filter>
<description>字符集過濾器</description>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>字符集編碼</description>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- druid -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!—將所有.do結尾的url請求交給DispatcherServlet處理 -->
<servlet>
<description>springmvc servlet</description>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>springmvc 配置文件</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.html</welcome-file>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
<!—超時配置,單位爲分鐘 -->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<!—錯誤跳轉頁面 -->
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/500.jsp</location>
</error-page>
</web-app>
可以看到,web.xml中引用了兩個xml配置文件:
classpath:spring.xml
classpath:springmvc.xml
其中,classpath就是代表web工程編譯後的/WEB-INF/classes/ 這個路徑(不理解的話,可以將war包放到tomcat中運行後,去工程目錄底下查看);在代碼工程中,把這兩個文件放到資源目錄(resources)下就可以了。
接下來,我們看spring.xml的配置:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
">
<importresource="config/*.xml"/>
<!-- 攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mappingpath="/**"/>
<beanclass="com.org.xcs.interceptor.LoginIntercepter"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
可以看到,這裏主要做了兩件事:一是配置了一個登錄攔截器,即用於攔截未登錄的請求,強制重定向到登錄頁面,攔截器內容我也貼出來供大家參考:
LoginIntercepter.java:
package com.visionvera.cms.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public classLoginIntercepter implements HandlerInterceptor {
public voidafterCompletion(HttpServletRequest arg0,
HttpServletResponsearg1, Object arg2, Exception arg3)
throws Exception {
}
public voidpostHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Objectarg2, ModelAndView arg3) throws Exception {
}
public booleanpreHandle(HttpServletRequest request, HttpServletResponse response,
Objectobj) throwsException {
Stringurl = request.getRequestURI().toString();
if(url !=null && !"".equals(url)&& isSkipCheck(url)){
return true;
}else{
Objectuser = request.getSession().getAttribute("login_user");
if(user ==null){
//判斷是否爲ajax請求
if (request.getHeader("x-requested-with")!=null
&&request.getHeader("x-requested-with")
.equalsIgnoreCase("XMLHttpRequest")){//如果是ajax請求響應頭會有x-requested-with
response.setHeader("sessionTimeOut","yes");
returnfalse;
}
response.sendRedirect(request.getContextPath()+"/index/showLogin.do");//如果用戶未登錄就強行重定向到登錄頁面
return false;
}
}
return true;
}
private boolean isSkipCheck(Stringurl){
String[]skipArr = {"login"};
for(String skip : skipArr){
if(url.toLowerCase().contains(skip)){
return true;
}
}
return false;
}
}
二是引入(import)了一些配置文件,這些配置文件存放於資源目錄下的config目錄中,都是xml格式的文件。
這裏我是爲了細分各種配置,所有拆分成了多個配置文件,大家可以根據實際情況進行整合,也是沒有問題的。
config目錄下,一共放有5個文件:
annotation.xml
jdbc.xml
mybatis.xml
property.xml
transation.xml
我們來逐個分析這些文件的內容:
annotation.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--spring 掃包 @Service.....-->
<context:component-scanbase-package="com.org.xcs">
<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:annotation-config/>
</beans>
這裏定義是掃描Service包,包的位置是在com.org.xcs,並排除了Controller(Controller包的掃描在springmvc.xml中定義)。
接着是jdbc.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- druid -->
<beanid="dataSource"class="com.alibaba.druid.pool.DruidDataSource"
init-method="init"destroy-method="close">
<!-- <property name="driverClass"value="${mysql.driver}"/> -->
<propertyname="url"value="${mysql.url}"></property>
<propertyname="username"value="${mysql.username}"/>
<propertyname="password"value="${mysql.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<propertyname="initialSize"value="1"/>
<propertyname="minIdle"value="1"/>
<propertyname="maxActive"value="20"/>
<!-- 配置獲取連接等待超時的時間 -->
<propertyname="maxWait"value="60000"/>
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
<propertyname="timeBetweenEvictionRunsMillis"value="60000"/>
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<propertyname="minEvictableIdleTimeMillis"value="300000"/>
<propertyname="validationQuery"value="SELECT 'x'"/>
<propertyname="testWhileIdle"value="true"/>
<propertyname="testOnBorrow"value="false"/>
<propertyname="testOnReturn"value="false"/>
<!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
<propertyname="poolPreparedStatements"value="true"/>
<propertyname="maxPoolPreparedStatementPerConnectionSize"
value="20"/>
<!-- 配置監控統計攔截的filters,去掉後監控界面sql無法統計 -->
<propertyname="filters"value="stat"/>
</bean>
</beans>
這裏定義了數據源(dataSource),使用阿里巴巴的druid數據庫連接池。
然後是mybatis.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--mybatis sessionFactory配置-->
<beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">
<propertyname="dataSource"ref="dataSource"/>
<propertyname="mapperLocations"value="classpath:com/visionvera/cms/dao/*.xml"/>
<propertyname="typeAliasesPackage"value="com.org.xcs.bean"/>
<propertyname="plugins">
<array>
<reflocal="statementHandlerInterceptor"/>
<reflocal="resultSetHandlerInterceptor"/>
</array>
</property>
</bean>
<beanid="statementHandlerInterceptor"class="com.org.xcs.interceptor.StatementHandlerInterceptor"/>
<beanid="resultSetHandlerInterceptor"class="com.org.xcs.interceptor.ResultSetHandlerInterceptor"/>
<!-- 掃包 -->
<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">
<propertyname="basePackage"value="com.org.xcs.dao"/>
</bean>
<!-- 註冊Spring工具類 -->
<beanid="springContextUtil"class="com.org.xcs.commom.SpringContextUtil"></bean>
</beans>
這裏主要是數據源的sessionFactory的定義,使用的是mybatis爲spring提供的SqlSessionFactoryBean工廠類。我們來重點分析工廠類定義的幾個property:
首先是dataSource,這個很明顯,定義的是數據源,指向的就是jdbc.xml中定義的那個;
其次是mapperLocations,定義mybatis的Mapper文件的路徑,是一些xml文件,mybatis的數據庫操作都是定義在這些文件中的;
再次是typeAliasesPackage,Mapper文件關聯的數據庫實體(entity)類的存放路徑;
最後是plugins,這個是mybatis提供的插件,可以定義攔截器用來實現分頁。定義多個攔截器的時候,執行順序從上到下。
兩個攔截器的內容如下:
StatementHandlerInterceptor.java:
package com.org.xcs.interceptor;
import java.sql.Connection;
import java.util.Properties;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.org.xcs.commom.ReflectUtil;
@Intercepts({ @Signature(type= StatementHandler.class, method ="prepare", args = { Connection.class }) })
public classStatementHandlerInterceptor implements Interceptor {
private static String MYSQL_DIALECT="com.org.xcs.dao.interceptor.MySqlDialectPPT";
private Loggerlog = LoggerFactory
.getLogger(StatementHandlerInterceptor.class);
public Object intercept(Invocationinvocation)throws Throwable {
RoutingStatementHandlerstatement = (RoutingStatementHandler)invocation
.getTarget();
PreparedStatementHandlerhandler = (PreparedStatementHandler) ReflectUtil
.getFieldValue(statement,"delegate");
RowBoundsrowBounds = (RowBounds) ReflectUtil.getFieldValue(handler,
"rowBounds");
if (rowBounds ==null || rowBounds.getLimit()== RowBounds.NO_ROW_LIMIT) {
returninvocation.proceed();
}
BoundSqlboundSql = statement.getBoundSql();
Stringsql = boundSql.getSql();
MySqlDialectPPTdialect = newMySqlDialectPPT();
sql= dialect.getLimitString(sql, rowBounds.getOffset(),
rowBounds.getLimit());
log.info(sql);
ReflectUtil.setFieldValue(boundSql,"sql",sql);
return invocation.proceed();
}
public Object plugin(Objecttarget) {
return Plugin.wrap(target,this);
}
public voidsetProperties(Properties properties) {
SqlSessionFactoryBeanc;
}
}
ResultSetHandlerInterceptor.java:
packagecom.org.xcs.interceptor;
importjava.sql.Statement;
importjava.util.Properties;
importorg.apache.ibatis.executor.resultset.FastResultSetHandler;
importorg.apache.ibatis.executor.resultset.ResultSetHandler;
importorg.apache.ibatis.plugin.Interceptor;
importorg.apache.ibatis.plugin.Intercepts;
importorg.apache.ibatis.plugin.Invocation;
importorg.apache.ibatis.plugin.Plugin;
importorg.apache.ibatis.plugin.Signature;
importorg.apache.ibatis.session.RowBounds;
importcom.org.xcs.commom.ReflectUtil;
@Intercepts({@Signature(type = ResultSetHandler.class, method ="handleResultSets", args = { Statement.class }) })
publicclass ResultSetHandlerInterceptor implements Interceptor {
public Object intercept(Invocationinvocation) throws Throwable {
FastResultSetHandler resultSet =(FastResultSetHandler) invocation
.getTarget();
RowBounds rowBounds = (RowBounds)ReflectUtil.getFieldValue(resultSet,
"rowBounds");
if (rowBounds.getLimit() > 0
&& rowBounds.getLimit() <RowBounds.NO_ROW_LIMIT) {
ReflectUtil.setFieldValue(resultSet,"rowBounds", new RowBounds());
}
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Propertiesproperties) {
}
}
另外使用到了ReflectUtil.java:
package com.org.xcs.commom;
importjava.lang.reflect.Field;
importjava.lang.reflect.Modifier;
public classReflectUtil {
public static void setFieldValue(Objectobject, String fieldName,Object value){
Field field = getDeclaredField(object,fieldName);
if (field == null)
throw newIllegalArgumentException("Could not find field ["
+ fieldName + "] ontarget [" + object + "]");
makeAccessible(field);
try {
field.set(object, value);
} catch(IllegalAccessException e) {
e.printStackTrace();
}
}
public static Object getFieldValue(Objectobject, String fieldName) {
Field field = getDeclaredField(object,fieldName);
if (field == null)
throw newIllegalArgumentException("Could not find field ["
+ fieldName + "] ontarget [" + object + "]");
makeAccessible(field);
Object result = null;
try {
result = field.get(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
private static FieldgetDeclaredField(Object object, String filedName) {
for (Class<?> superClass =object.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass()) {
try {
returnsuperClass.getDeclaredField(filedName);
} catch (NoSuchFieldException e) {
// Field 不在當前類定義, 繼續向上轉型
}
}
return null;
}
private static void makeAccessible(Fieldfield) {
if(!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
}
}
以上是分頁攔截器所用到的三個類。接下來我們舉一個Mapper的例子:
TestDao.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mappernamespace="com.org.xcs.dao.TestDao">
<selectid="getTimeCount" resultType="ReportVO"parameterType="map">
…<!--省略-->
</select>
<selectid="getMemberCount" resultType="ReportVO"parameterType="map">
…<!--省略-->
</mapper>
可以看到,TestDao.xml的mapper有個namespace屬性,這個屬性是用於綁定Dao接口的。當你的namespace綁定接口後,你可以不用寫接口實現類,mybatis會通過該綁定自動幫你找到對應要執行的SQL語句。這裏指定爲TestDao,我們來看下TestDao.java的內容:
package com.org.xcs.dao;
importjava.util.List;
importjava.util.Map;
import com.org.xcs.bean.ReportVO;
publicinterface MainDao {
List<ReportVO>getTimeCount(Map<String, Object> paramsMap);
List<ReportVO>getMemberCount(Map<String, Object> paramsMap);
}
可以看到,TestDao.java裏只定義了接口。通過namespace綁定後,我們通過Service調用Dao接口時,mybatis會自動幫我們執行mapper中定義好的數據庫操作語句。
再看property.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 讀取jdbc配置 -->
<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<propertyname="locations">
<list>
<!-- jdbc配置 -->
<value>classpath:properties/jdbc.properties</value>
<!-- memcached配置 -->
</list>
</property>
</bean>
</beans>
這個比較簡單,就是定義了jdbc.properties的存放路徑。
jdbc.properties定義了數據庫的地址、用戶名密碼等信息:
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=UTF-8
mysql.username=test
mysql.password=123456
最後是transation.xml:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- spring 事務 -->
<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertyname="dataSource"ref="dataSource"/>
</bean>
<!-- 開啓事務註解 -->
<tx:annotation-driventransaction-manager="transactionManager"/>
</beans>
這個文件定義了數據源使用的事務類型。這裏我使用的是spring自帶的DataSourceTransactionManager。它會將所有具有@Transactional 註解的bean自動配置爲聲明式事務支持。
注意:要使事務生效,必須在接口或類的聲明處 ,寫一個@Transactional,例如:
package com.org.xcs.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.org.xcs.bean.ReportVO;
import com.org.xcs.service.MainService;
@Service
@Transactional
public class MainServiceImpl implements MainService {
…<-- 省略 -->
}
至此,一個完整的SSM框架完成了。其中若有遺漏或錯誤,歡迎大家批評指正。