SSH框架网上商城项目第1战之整合Struts2、Hibernate4.3和Spring4.2
本文开始做一个网上商城的项目,首先从搭建环境开始,一步步整合s2sh。这篇博文主要总结一下如何整合struts2、hibernate4.3和spring4.2。
整合三大框架得先从搭建各部分环境开始,也就是说首先得把spring,hibernate和struts2的环境搭建好,确保它们没有问题了,再做整合。这篇博文遵从的顺序是:先搭建spring环境-->然后搭建hibernate环境--> 整合spring和hibernate --> 搭建struts2环境 --> 整合spring和struts2。
1. 整个项目jar包的管理
spring的jar包很多,开发的时候建议将它们分个类,然后依次添加到user library,方便管理,也一目了然。这里我总结一下整个ssh所需要的基本jar包,看下图:
从图中可以看出,首先将spring的jar分为四类:spring-4.2.4-core、spring-4.2.4-aop、spring-4.2.4-persistence以及spring-4.2.4-web。将spring的核心包都放到core中,与aop相关的都放到aop中,与持久化(与hibernate整合)相关的放到persistence中,与web(与struts2整合)相关的放到web中。每个部分都有哪些jar包呢?请看下面的截图:
注:以上每个分类的包中,并非包含原来包中所有的jar,有些jar文件并没有用到,等具体项目需要的时候再往里加就行了,上图是保证项目的环境可以搭建所需要的的最基本的jar包。
2.搭建spring环境
上面的jar包截图是最后整合好的所有jar包,刚开始搭建环境的时候不需要一次性全部加进来,可以一点一点的加,这样也更利于理解每个部分的jar包都有什么作用,当然,一次都加进来也是可以的。
2.1 添加配置文件beans.xml和相应的jar包
新建一个工程,然后添加在user library中添加自己的库,这里主要添加两个,即spring-4.2.4-core和spring4.2.4-aop,添加jar包不再赘述。添加完了后,在src目录下添加beans.xml文件,这个文件的模板网上很多,spring自带的例子里也有,考一份过来就行,见下图:
2.2 测试spring的ioc环境
我们写一个普通的java类java.util.date类来测试一下spring ioc是否正常,如果在测试程序中能正常注入,则说明spring的ioc环境搭建成功,下面我们写一个测试用例:
/** * @description todo(采用spring的注解调试,仅仅支持spring3.1及以上) * @author ni shengwu * */ /* * spring3.1后多了个spring-test-4.2.4.release.jar包,这个jar包专门用来支持junit基于注解的测试的,该jar包在spring-4.2.4-core中 * 该jar包里有个springjunit4classrunner.class,用@runwith注解加进来即可 * * 注解@contextconfiguration表示将applicationcontext对象注入进来,就不用像以往那样在测试程序里先new了,直接使用 */ @runwith(springjunit4classrunner.class) @contextconfiguration(locations="classpath:beans.xml") public class sshtest { @resource private date date; @test //测试spring ioc的开发环境 public void springioc() { system.out.println(date); } }
最后能正常输出:thu apr 28 22:45:13 cst 2016。说明date对象已经被spring给注入进来了,从而验证了spring ioc功能正常,为此,spring的环境搭建完成。
3. 搭建hibernate环境
hibernate的环境搭建相比于spring要复杂一些,因为用到了myeclipse中的逆向工程。我们按以下步骤来搭建hibernate开发环境:
3.1 添加相应的jar包
这里主要是向user library中添加两项jar包:hibernate4.3.11和mysql驱动包mysql-connector-java-5.1.26,不再赘述。
3.2 新建数据库和表
drop database if exists shop; create database shop default character set utf8; use shop; drop table if exists category; create table category ( /* 类别编号,自动增长 */ id int not null auto_increment, /* 类别名称 */ type varchar(20), /* 类别是否为热点类别,热点类别才有可能显示在首页*/ hot bool default false, /* 设置类别编号为主键*/ primary key (id) );
3.3 db浏览器连接到mysql数据库
db浏览器指的是myeclipse中的一个视图窗口,可以很直观的看到mysql中都有哪些数据库和表,打开db浏览器的方法:window->open perspective->db browser打开db浏览器工作窗口。如果没有db browser,按照下面:window->show view->other->输入db browser,找到打开即可。
打开后,我们开始连接到mysql数据库了,在db browser窗口的空白处右键->new,就会弹出下面的对话框:
填好后,点击test driver测试一下,测试通过表示database connection driver已经配置好,然后finish即可,这样我们就能在db浏览器窗口中看到数据库mysql5.6了,右键打开就能看到数据库中已有的库和表了,如下:
3.4 创建xml映射文件和sessionfactory
sessionfactory是用来创建session的,我们通过如下方式创建:工程名右键->myeclipse->add hibernate capabilities,如果没有add hibernate capabilities,就点击project facets->install hibernate facets,会弹出如下窗口:
下一步,在myeclipse中添加hibernate support,即hibernate.cfg.xml映射文件和sessionfactory。这里主要是给sessionfactory建个包,默认包不行。
下一步,添加驱动,由于我们之前已经配置好了一个驱动了,所以在这里直接选择刚刚配置好的即可。
下一步,由于之前我们已经添加过自己的jar保留,这里不用选择,直接finish。
这样我们就完成了hibernate配置文件和sessionfactory的创建。下面我们简单看一下myeclipse创建的sessionfactory里面都有啥:
public class hibernatesessionfactory { private static final threadlocal<session> threadlocal = new threadlocal<session>(); //sessionfactory中用的是线程池技术 private static org.hibernate.sessionfactory sessionfactory; //sessionfactory:创建session的工厂 private static configuration configuration = new configuration(); private static serviceregistry serviceregistry; static { //类加载时初始化sessionfactory try { configuration.configure(); serviceregistry = new serviceregistrybuilder().applysettings(configuration.getproperties()).buildserviceregistry(); sessionfactory = configuration.buildsessionfactory(serviceregistry); //hibernate4中创建sessionfactory的方法 } catch (exception e) { system.err.println("%%%% error creating sessionfactory %%%%"); e.printstacktrace(); } } private hibernatesessionfactory() { //私有构造方法阻止new出对象,保证sessionfactory的单例 } public static session getsession() throws hibernateexception { session session = (session) threadlocal.get(); //从线程池中拿session if (session == null || !session.isopen()) { //如果线程池是空的,或者session打开失败 if (sessionfactory == null) { rebuildsessionfactory(); //如果sessionfactory是空的,那就再创建一次,和static部分一样的 } session = (sessionfactory != null) ? sessionfactory.opensession() : null; //sessionfactory不为空就创建一个session threadlocal.set(session); //然后把这个session放到线程池中,下次再拿 } return session; } public static void rebuildsessionfactory() { try { configuration.configure(); serviceregistry = new serviceregistrybuilder().applysettings(configuration.getproperties()).buildserviceregistry(); sessionfactory = configuration.buildsessionfactory(serviceregistry); } catch (exception e) { system.err.println("%%%% error creating sessionfactory %%%%"); e.printstacktrace(); } } public static void closesession() throws hibernateexception { session session = (session) threadlocal.get(); threadlocal.set(null); if (session != null) { session.close(); } } public static org.hibernate.sessionfactory getsessionfactory() {//提供一个公共接口让外界获得这个单例sessionfactory return sessionfactory; } public static configuration getconfiguration() { return configuration; } }
从创建的hibernatesessionfactory中可以看出,主要用到了单例模式和线程池技术。也不难理解。
3.5 通过逆向工程生成model、orm映射文件
下面开始使用逆向工程创建实例对象了,也就是数据库的表对应的model。在db browsera窗口右击我们刚刚创建的表shop,选择hibernate reverse engineering开始创建:
创建的时候有两种方式,基于配置文件的和基于注解的,具体的看开发人员心情了,可以选择:
然后下一步,选择一下native主键自增方式,然后便完成了逆向工程创建model和orm映射了。
完成后,会有category的model生成,同时在hibernate.cfg.xml文件中也会生成相应的映射,前面基于配置文件的和基于注解的在hibernate.cfg.xml中生成的映射时不同的。
3.6 测试hibernate持久化数据库
因为这里还没有与spring整合,只是单纯的搭建hibernate开发环境,所以我们没有必要使用注解,我们通过直接new一个service的方式执行数据入库。
先写categoryservice接口和实现类:
public interface categoryservice { public void save(category category); //用来测试hibernate环境 } public class categoryserviceimpl implements categoryservice { @override //没有和spring整合的情况 public void save(category category) { //通过刚刚生成的sessionfactory获取session session session = hibernatesessionfactory.getsession(); try { //手动事务 session.gettransaction().begin(); //执行业务逻辑 session.save(category); //手动提交 session.gettransaction().commit(); } catch(exception e) { session.gettransaction().rollback(); throw new runtimeexception(e); } finally { hibernatesessionfactory.closesession(); } } }
下面在刚刚的测试用例中添加对hibernate的测试:
@runwith(springjunit4classrunner.class) @contextconfiguration(locations="classpath:beans.xml") public class sshtest { @resource private date date; @test //测试spring ioc的开发环境 public void springioc() { system.out.println(date); } @test //测试hibernate的开发环境,因为没有整合,可以直接new public void hihernate() { categoryservice categoryservice = new categoryserviceimpl(); category category = new category("男士休闲", true); categoryservice.save(category); } }
我们查看数据库,发现多了刚刚插入的这项,说明hibernate环境没有问题。至此,hibernate的开发环境我们就搭建好了。
4. 整合spring和hibernate
搭建好了spring和hibernate的开发环境后,我们开始整合这两者。整合spring和hibernate后就可以使用aop让spring来管理hibernate的事务了。整合spring和hibernate主要从两大方面入手,一是导入必要的jar包,二是配置beans.xml文件。下面我们一步步整合spring和hibernate。
4.1 导入相应的jar包
整合spring和hibernate时需要导入的jar包有两大块,spring4.2.4-persistence和c3p0-0.9.5.1,每一块jar包中的具体jar文件请参见上面的截图,这里不再赘述。下面开始配置beans.xml文件了。
4.2 配置数据源datasource
首先配置一下datasource,然后hibernate.cfg.xml中相应的部分可以干掉了。因为在spring中配置好了,spring会去初始化这个datasource,也就是说这就交给spring来完成了,hibernate.cfg.xml中的相应部分可以删掉了。如下:
<!-- com.mchange.v2.c3p0.combopooleddatasource类在c3p0-0.9.5.1.jar包的com.mchange.v2.c3p0包中 --> <bean id="datasource" class="com.mchange.v2.c3p0.combopooleddatasource"> <property name="driverclass" value="com.mysql.jdbc.driver" /> <property name="jdbcurl" value="jdbc:mysql://localhost:3306/shop" /> <property name="user" value="root" /> <property name="password" value="root" /> </bean>
hibernate.cfg.xml中需要干掉的部分:
4.3 配置sessionfactory
配置sessionfactory是用来产生一个session的,另外hibernatetemplate也可以,但是这里采用sessionfactory而不用hibernatetemplate,是因为hibernatetemplate是spring提供的,依赖于spring,如果哪天不用spring了,就会报错。而sessionfactory是hibernate提供的,没有问题。hibernatetemplate的依赖性太强了。下面看一下具体配置:
<!-- org.springframework.orm.hibernate4.localsessionfactorybean类在spring-orm-4.2.4.release.jar包的org.springframework.orm.hibernate4包中 --> <bean id="sessionfactory" class="org.springframework.orm.hibernate4.localsessionfactorybean"> <property name="datasource" ref="datasource" /> <property name="configlocation" value="classpath:hibernate.cfg.xml" /> <!-- 加载hibernate配置文件 --> </bean>
sessionfactory中的datasource我们用刚刚配好的datasource,用ref属性引用进来即可,configlocation我们不在这配了,直接加载hibernate.cfg.xml文件,使用hibernate配置文件中的配置,更加简洁方便。
4.4 配置事务管理器
配置事务管理器,是用来管理sessionfactory的,这样所有的由sessionfactory产生的session将会有声明式的管理。配置如下:
<!-- org.springframework.orm.hibernate4.hibernatetransactionmanager类spring-orm-4.2.4.release.jar包的org.springframework.orm.hibernate4包中 --> <bean id="transactionmanager" class="org.springframework.orm.hibernate4.hibernatetransactionmanager"> <property name="sessionfactory" ref="sessionfactory" /> </bean>
同样,sessionfactory我们用刚刚配置好的sessionfactory,用ref属性引用进来即可。到这里就会发现,从上面一直配下来,都是一连串的操作,一个个引用下来。
4.5 配置advice(通知)
配置advice的目的是指定哪些方法需要什么类型的事务模式。看配置:
<tx:advice id="advice" transaction-manager="transactionmanager"> <tx:attributes> <tx:method name="save*" propagation="required"/> <tx:method name="update*" propagation="required"/> <tx:method name="delete*" propagation="required"/> <tx:method name="*" propagation="supports"/> </tx:attributes> </tx:advice>
required表示如果存在事务,则支持当前的事务,如果没有则创建一个新的事务,这个事务模式应用在所有以save、update和delete开头的方法上,也就是说对数据库的增删改的时候需要添加事务支持。supports表示如果存在事务,则支持当前的事务,如果没有就算了。
4.6 配置aop切面
<aop:config> <!-- 配置哪些包的类要切入事务 --> <aop:pointcut id="pointcut" expression="execution(* cn.it.shop.service.impl.*.*(..))" /> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/><!-- 连接了<span style="font-family:microsoft yahei;">上</span>面的advice和上面的pointcut --> <!-- aop:pointcut要写在aop:advisor上面,否则会报错 --> </aop:config>
aop即面向切面编程,aop:pointcut定义一个切面,expression属性中配置的意思是所有cn.it.shop.service.impl包下的所有方法,不管返回值和参数是什么,都要切入事务。该包是属于dao层的,直接操作数据库的。aop:advice将通知和切面结合起来,我们直接使用上面配置好的advice和pointcut,将其引入进来即可。这样配置好了后,意思就是说,凡是cn.it.shop.service.impl包下的方法都需要切入事务管理,具体地,以save、update、delete开头的方法使用requied方式,其他方法使用supports方式。这样就很好理解这个配置的意思了。
4.7 测试整合结果
之前搭建hibernate环境的时候,我们测试是直接new了一个service来操作数据库,因为当时还没有和spring整合。现在通过配置beans.xml后,让spring去管理hibernate的事务了,所以现在的测试要把service交给spring管理,通过spring注入进来,并且依赖sessionfactory,如果能插入数据到数据库,则说明声明事务ok。
首先,我们要在spring的配置文件beans.xml中配一下这个service:
<property name="sessionfactory" ref="sessionfactory" /><!-- 依赖的sessionfactory用我们之前配好的sessionfactory-->
</bean>
其次,我们需要在categoryservice接口和它的实现类categoryserviceimpl中增加一个方法用来测试整合后的情况:
public interface categoryservice { public void save(category category); //用来测试hibernate环境 public void update(category category); //用来测试spring和hibernate整合后 } public class categoryserviceimpl implements categoryservice { @override //没有和spring整合的情况 public void save(category category) { //通过工具类获取session session session = hibernatesessionfactory.getsession(); try { //手动事务 session.gettransaction().begin(); //执行业务逻辑 session.save(category); //手动提交 session.gettransaction().commit(); } catch(exception e) { session.gettransaction().rollback(); throw new runtimeexception(e); } finally { hibernatesessionfactory.closesession(); } } /*spring和hibernate整个后*/ private sessionfactory sessionfactory; //定义一个sessionfactory //当需要使用sessoinfactory的时候,spring会将sessionfactory注入进来 public void setsessionfactory(sessionfactory sessionfactory) { this.sessionfactory = sessionfactory; } protected session getsession() { //从当前线程获取session,如果没有则创建一个新的session return sessionfactory.getcurrentsession(); } @override //spring和hibernate整合后的情况 public void update(category category) { getsession().update(category); } }
现在我们可以去测试类中增添测试方法,来测试spring和hibernate整合后的结果了:
@runwith(springjunit4classrunner.class) @contextconfiguration(locations="classpath:beans.xml") public class sshtest { @resource private date date; @resource private categoryservice categoryservice; @test //测试spring ioc的开发环境 public void springioc() { system.out.println(date); } @test //测试hibernate的开发环境,因为没有整合,可以直接new public void hihernate() { categoryservice categoryservice = new categoryserviceimpl(); category category = new category("男士休闲", true); categoryservice.save(category); } @test //测试hibernate和spring整合后 public void hibernateandspring() { categoryservice.update(new category(1, "休闲女式", true)); //categoryservice通过spring从上面注入进来的 } }
然后我们查看数据库,发现id=1的category被修改成了休闲女式了,说明更新成功。至此,spring和hibernate整合成功。
5. 搭建struts2环境
5.1 添加相应的配置和jar包
struts2运行所需的jar包我放在struts2.3.41的library中了,直接引入即可,不再赘述。另外,还要对web.xml文件进行配置,配置如下:
<?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" id="webapp_id" version="3.0"> <display-name>e_shop</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.strutsprepareandexecutefilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping> </web-app>
如上,我们配置了一个strutsprepareandexecutefilter过滤器,并将过滤器的url-pattern设置为*.action,即所有.action后缀的都会首先经过这个过滤器,这也是struts2的入口处。
5.2 创建action并且配置到struts.xml文件中
我们创建一个action如下:
public class categoryaction extends actionsupport { private categoryservice categoryservice; //设置categoryservice是为了很直观的看出与spring整合前后的不同 public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public string update() { system.out.println("----update----"); system.out.println(categoryservice); //整合前后输出不同 return "index"; } public string save() { system.out.println("----save----"); system.out.println(categoryservice);//整合前后输出不同 return "index"; } }
然后我们配置struts.xml文件,该文件放在src目录下:
<?xml version="1.0" encoding="utf-8" ?> <!doctype struts public "-//apache software foundation//dtd struts configuration 2.3//en" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <package name="shop" extends="struts-default"> <!-- category_update.actiocan: 访问update方法 --> <action name="category_*" class="cn.it.shop.action.categoryaction" method="{1}"> <result name="index">/index.jsp</result> </action> </package> </struts>
5.3 测试struts2环境
测试方法是:写一个jsp访问action,如果action可以创建,则表示struts2环境ok。即struts2的一连串流程可以正常走完:jsp-->struts.xml-->action-->struts.xml-->jsp,这样struts2的环境就算搭好了。我们写一个简单的index.jsp
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>my jsp 'index.jsp' starting page</title> </head> <body> <!-- 下面两种写法都可以访问 --></span> <a href="${pagecontext.request.contextpath }/category_update.action">访问update</a> <a href="category_save.action">访问save</a> </body> </html>
然后我们部署以下工程,打开tomcat服务器,在浏览器中输入:http://localhost:8080/e_shop/index.jsp后,能出现正常jsp页面,然后点击两个按钮,仍然跳转到index.jsp,然后我们看一下控制台的输出信息:
----update----
null
----save----
null
说明struts2的一条线走完了,环境没有问题,至此,struts2开发环境搭建完毕。
我们看控制台输出了null,也就是说categoryservice是空,也就是说根本没拿到categoryservice,因为我们没有和spring进行整合,没有被注入进来,所以null是正常的。我们沿着控制台输出的信息往上翻,会发现一条信息:choosing bean (struts) for (com.opensymphony.xwork2.objectfactory)。括号里是struts说明没有和spring整合前,action是有struts2产生的。
6. spring和struts2整合
6.1 添加相应的jar包
spring与struts2整合时的jar包主要在spring4.2.4-web里面,里面包括struts2-spring-plugin-2.3.24.1.jar,导包不再赘述。
6.2 把action和它的依赖交给spring管理
在spring的配置文件beans.xml中配置action和它的依赖,我们目前只有一个action,配置如下所示:
<bean id="date" class="java.util.date" /> <bean id="categoryaction" class="cn.it.shop.action.categoryaction" scope="prototype"> <property name="categoryservice" ref="categoryservice" /> <!-- 依赖的categoryservice用上面和hibernate整合时配置好的categoryservice --> </bean>
6.3 修改struts.xml中的配置
原来在struts.xml中,class属性对应的是具体action的完全限定名,现在将class属性的值改为spring中配置action的id值,即categoryaction,如下:
<struts> <package name="shop" extends="struts-default"> <!-- class对应的是spring中配置该action的id值,因为要交给spring管理 --> <action name="category_*" class="categoryaction" method="{1}"> <result name="index">/index.jsp</result> </action> </package> </struts>
6.4 配置监听器
在web.xml中配置监听器contextloaderlistener,这样在服务器启动的时候就可以加载spring的配置文件了。如下:
<?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" id="webapp_id" version="3.0"> <display-name>e_shop</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.strutsprepareandexecutefilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping> <!-- web.xml中监听器的启动优先级要高于过滤器,所以配在下面无所谓的 --> <listener> <listener-class>org.springframework.web.context.contextloaderlistener</listener-class> </listener> <context-param> <param-name>contextconfiglocation</param-name> <param-value>classpath:beans.xml</param-value> </context-param> </web-app>
6.5 测试整合结果
我们在action中新加一句更新数据库的语句,如下:
public class categoryaction extends actionsupport { private category category;//设置一个私有成员变量接收url带过来的参数,注意下面要写好get和set方法 private categoryservice categoryservice; public void setcategoryservice(categoryservice categoryservice) { this.categoryservice = categoryservice; } public string update() { system.out.println("----update----"); system.out.println(categoryservice);//由于已经和spring整合,所以可以拿到这个categoryservice了,打印出来就不是null了 categoryservice.update(category); //新加一条语句,来更新数据库 return "index"; } public string save() { system.out.println("----save----"); system.out.println(categoryservice); return "index"; } public category getcategory() { return category; } public void setcategory(category category) { this.category = category; } }
然后我们修改一下index.jsp文件,如下:
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>my jsp 'index.jsp' starting page</title> </head> <body> <a href="${pagecontext.request.contextpath }/category_update.action?category.id=2&category.type=gga&category.hot=false">访问update</a> <a href="category_save.action">访问save</a> </body> </html>
然后我们部署以下工程,打开tomcat服务器,在浏览器中输入:http://localhost:8080/e_shop/index.jsp后,能出现正常jsp页面,然后点击“访问update”按钮,仍然跳转到index.jsp,然后我们看一下控制台的输出信息:
----update---- cn.it.shop.service.impl.categoryserviceimpl@7c5ecf80 hibernate: update category set hot=?, type=? where id=?
我们看到可以输出categoryservice这个对象的信息了,也可以输出执行update语句时的sql语句,然后我们查询一下数据库,发现id=2的数据的type被更新为gga,hot更新为false。我们沿着控制台输出的信息往上翻,会发现一条信息:choosing bean (spring) for (com.opensymphony.xwork2.objectfactory),括号里为spring,与上面的情况对比可知,struts2在与spring整合后,action交给了spring去管理了。
至此,struts2、hibernate4和spring4整合工作已经全部完成,接下来就可以在ssh环境下进行开发了!
本文介绍的ssh整合中所需要的完整jar包:免费下载
整个项目的源码下载地址:
原文地址:
(注:到最后提供整个项目的源码下载!欢迎大家收藏或关注)
以上就是本文的全部内容,希望能给大家一个参考,也希望大家多多支持。