This article starts with an online mall project, starting with the construction of the environment and integrating S2SH step by step. This blog post mainly summarizes how to integrate Struts2, Hibernate4.3 and Spring4.2.
To integrate the three major frameworks, you must first start by building various environments, that is, first, you must build the environments of Spring, Hibernate and Struts2 to ensure that they have no problems and then integrate them. The order in which this blog post follows is: first build the Spring environment --> then build the Hibernate environment --> integrate Spring and Hibernate --> build the Struts2 environment --> integrate Spring and Struts2.
1. Management of the entire project jar package
Spring has many jar packages. It is recommended to classify them in a category and then add them to the User Library in turn for easy management and clear at a glance. Here I will summarize the basic jar packages required for the entire SSH and see the following picture:
As can be seen from the figure, firstly, Spring jars are divided into four categories: spring-4.2.4-core, spring-4.2.4-aop, spring-4.2.4-persistence and spring-4.2.4-web. Put all the core packages of spring into core, put all related to AOP into AOP, put all related to persistence (integrated with Hibernate) into persistence, and put all related to web (integrated with struts2) into web. What jar packages do each part have? Please see the screenshot below:
Note: The packages in each category above do not contain all the jars in the original package. Some jar files are not used. Just add them when the specific project needs it. The picture above is the most basic jar package required to ensure that the project environment can build the most basic jar package.
2. Build a Spring environment
The above screenshot of the jar package is all the jar packages that were integrated in the last time. When you first build the environment, you don’t need to add them all at once. You can add them bit by bit. This is also more conducive to understanding what the jar packages in each part have. Of course, it is also possible to add them all at once.
2.1 Add configuration file beans.xml and corresponding jar packages
Create a new project, and then add your own library to the User Library. Here are two main ones, namely spring-4.2.4-core and spring4.2.4-aop. I won't go into details about adding jar packages. After adding, add the beans.xml file in the src directory. There are many templates for this file online, and there are also examples provided by Spring. Just take a copy, see the following picture:
2.2 Test Spring's IoC environment
Let's write a normal java class java.util.Date class to test whether Spring IoC is normal. If it can be injected normally in the test program, it means that Spring's IoC environment is successfully built. Let's write a test case below:
/** * @Description TODO (using Spring's annotation debugging, only supports Spring3.1 and above) * @author Ni Shengwu * */ /* * After Spring3.1, there is an additional spring-test-4.2.4.RELEASE.jar package. This jar package is specially used to support JUnit annotation-based testing. The jar package is in spring-4.2.4-core* There is a SpringJUnit4ClassRunner.class in this jar package, and you can add it with the @RunWith annotation* * Annotation @ContextConfiguration means injecting the ApplicationContext object, you don't have to new in the test program as before, just use */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:beans.xml") public class SSHTest { @Resource private Date date; @Test //Test The development environment of Spring IOC public void springIoc() { System.out.println(date); } } Finally, it can output normally: Thu Apr 28 22:45:13 CST 2016. This indicates that the Date object has been injected by Spring, which verifies that the Spring IoC function is normal. For this reason, the Spring environment is built.
3. Build a Hibernate environment
Hibernate's environment construction is more complicated than Spring because it uses reverse engineering in MyEclipse. We follow the following steps to build the Hibernate development environment:
3.1 Add the corresponding jar package
Here we mainly add two jar packages to the User Library: hibernate4.3.11 and the MySQL driver package mysql-connector-java-5.1.26, which will not be described in detail.
3.2 Create a new database and table
drop database if exists shop; create database shop default character set utf8; use shop; drop table if exists category; create table category ( /* Category number, automatic growth*/ id int not null auto_increment, /* Category name*/ type varchar(20), /* Whether the category is a hot category, the hot category may be displayed on the home page*/ hot bool default false, /* Set the category number to the primary key*/ primary key (id) );
3.3 DB browser connects to Mysql database
DB browser refers to a view window in MyEclipse. You can intuitively see which databases and tables are in MySQL. The method to open the DB browser: Window->Open Perspective->DB Browser opens the DB browser working window. If there is no DB Browser, follow the following: Window->Show View->other->Enter DB Browser and find it to open it.
After opening, we start connecting to the MySQL database. Right-click ->new in the blank space of the DB Browser window, and the following dialog box will pop up:
After filling in, click Test Driver to test it. The test passes the test and indicates that the DataBase connection Driver has been configured, and then finish it. In this way, we can see the database MySQL 5.6 in the DB browser window. Right-click to open it to see the existing libraries and tables in the database, as follows:
3.4 Create xml mapping file and sessionFactory
SessionFactory is used to create sessions. We create them in the following way: Right-click the project name ->myeclipse->Add Hibernate Capabilities. If there is no Add Hibernate Capabilities, click project facets->Install Hibernate Facets, and the following window will pop up:
Next, add Hibernate Support, that is, hibernate.cfg.xml mapping file and sessionFactory in MyEclipse. Here we mainly create a package for sessionFactory, and the default package cannot be used.
Next, add a driver. Since we have configured a driver before, we can directly select the newly configured driver here.
Next, since we have added our own jar reservation before, there is no need to choose here, just finish it directly.
In this way, we complete the creation of the Hibernate configuration file and sessionFactory. Let's take a brief look at what is in the sessionFactory created by MyEclipse:
public class HibernateSessionFactory { private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>(); //sessionFactory is used in sessionFactory private static org.hibernate.SessionFactory sessionFactory; //sessionFactory: Create a factory for session private static Configuration configuration = new Configuration(); private static ServiceRegistry serviceRegistry; static { //Initialize sessionFactory try { configuration.configure(); serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry(); sessionFactory = configuration.buildSessionFactory(serviceRegistry); // Method to create sessionFactory in Hibernate4} catch (Exception e) { System.err.println("%%%%% Error Creating SessionFactory %%%%%"); e.printStackTrace(); } } private HibernateSessionFactory() { //Private constructor method prevents new objects from being released, ensuring that sessionFactory singleton} public static Session getSession() throws HibernateException { Session session = (Session) threadLocal.get(); //Get session from the thread pool if (session == null || !session.isOpen()) { //If the thread pool is empty, or the session opening fails if (sessionFactory == null) { rebuildSessionFactory(); //If the sessionFactory is empty, create it again, the same as the static part} session = (sessionFactory != null) ? sessionFactory.openSession() : null; //SessionFactory is not empty, create a session threadLocal.set(session); //Then put this session into the thread pool and get it next time} 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() {//Provide a public interface to allow the outside world to obtain this singleton sessionFactory return sessionFactory; } public static Configuration getConfiguration() { return configuration; } } It can be seen from the HibernateSessionFactory created that singleton pattern and thread pooling technology are mainly used. It's not difficult to understand.
3.5 Generate model and orm mapping files through reverse engineering
Next, we will start using reverse engineering to create an instance object, that is, the model corresponding to the database table. In the DB Browsera window, right-click the table shop we just created and select Hibernate Reverse Engineering to start creating:
There are two ways to create it, based on configuration files and based on annotations. It depends on the developer's mood. You can choose:
Then, in the next step, select the native primary key autoincrement method, and then complete the reverse engineering to create model and orm mapping.
After completion, Category's model will be generated, and corresponding mappings will also be generated in the hibernate.cfg.xml file. The previous mappings based on configuration files and annotations based on annotations will be different.
3.6 Testing Hibernate persistent database
Because it has not been integrated with Spring, it is just a simple construction of the Hibernate development environment, so we do not need to use annotations. We execute data entry into the database by directly new service.
First write the CategoryService interface and implementation class:
public interface CategoryService { public void save(Category category); //Use to test the Hibernate environment} public class CategoryServiceImpl implements CategoryService { @Override //There is no integration with Spring public void save(Category category) { //Get session Session session = HibernateSessionFactory.getSession(); try { //Manual transaction session.getTransaction().begin(); //Execute business logic session.save(category); //Manually submit session.getTransaction().commit(); } catch(Exception e) { session.getTransaction().rollback(); throw new RuntimeException(e); } finally { HibernateSessionFactory.closeSession(); } } } The following is to add the test of Hibernate in the test case just now:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:beans.xml") public class SSHTest { @Resource private Date date; @Test //Test The development environment of Spring IOC public void springIoc() { System.out.println(date); } @Test //Test / Test the development environment of Hibernate, because there is no integration, you can directly new public void hihernate() { CategoryService categoryService = new CategoryServiceImpl(); Category category = new Category("Men's Casual", true); categoryService.save(category); } }We checked the database and found that there were too many items that were just inserted, which means there was no problem with the Hibernate environment. At this point, we have built the Hibernate development environment.
4. Integrate Spring and Hibernate
After building the development environment for Spring and Hibernate, we began to integrate the two. After integrating Spring and Hibernate, you can use AOP to let Spring manage Hibernate transactions. Integrating Spring and Hibernate mainly starts from two aspects: one is to import the necessary jar packages, and the other is to configure the beans.xml file. Below we integrate Spring and Hibernate step by step.
4.1 Import the corresponding jar package
There are two major jar packages that need to be imported when integrating Spring and Hibernate, spring4.2.4-persistence and c3p0-0.9.5.1. For the specific jar files in each jar package, please refer to the screenshot above, and will not be described here. Now we will start configuring the beans.xml file.
4.2 Configuring the data source dataSource
First configure the dataSource, and then the corresponding part in hibernate.cfg.xml can be killed. Because it is configured in Spring, Spring will initialize this dataSource, which means that this is left to Spring to complete, and the corresponding part in hibernate.cfg.xml can be deleted. as follows:
<!-- com.mchange.v2.c3p0.ComboPooledDataSource class is in the com.mchange.v2.c3p0 package of c3p0-0.9.5.1.jar package --> <bean id="dataSource"> <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>
The part that needs to be killed in hibernate.cfg.xml:
4.3 Configuring sessionFactory
The sessionFactory is configured to generate a session. In addition, HibernateTemplate is also possible. However, the sessionFactory is used here instead of HibernateTemplate because HibernateTemplate is provided by Spring and depends on Spring. If Spring is not used one day, an error will be reported. And the sessionFactory is provided by Hibernate, no problem. HibernateTemplate is too dependent. Let’s take a look at the specific configuration:
<!-- org.springframework.orm.hibernate4.LocalSessionFactoryBean class in org.springframework.orm.hibernate4 package of spring-orm-4.2.4.RELEASE.jar package --> <bean id="sessionFactory"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <!-- Load hibernate configuration file --> </bean>
We just used the dataSource in the sessionFactory to reference the dataSource with the ref attribute. We are no longer using the configLocation here. We directly load the hibernate.cfg.xml file and use the configuration in the hibernate configuration file to make it more concise and convenient.
4.4 Configuring the Transaction Manager
The configuration transaction manager is used to manage sessionFactory, so that all sessions generated by sessionFactory will be managed declaratively. The configuration is as follows:
<!-- org.springframework.orm.hibernate4.HibernateTransactionManager class spring-orm-4.2.4.RELEASE.jar package in org.springframework.orm.hibernate4 package--> <bean id="transactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
Similarly, we can use the sessionFactory just configured and reference it with the ref attribute. At this point, you will find that all the way from above is a series of operations, citing them one by one.
4.5 Configure advice (notification)
The purpose of configuring advice is to specify which methods require what type of transaction mode. See the configuration:
<tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED"/> <tx:attributes> </tx:attributes> </tx:advice>
REQUIRED means that if there is a transaction, the current transaction is supported. If there is no, a new transaction is created. This transaction mode is applied to all methods starting with save, update and delete, that is, transaction support is required when adding, deleting and modifying the database. SUPPORTS means that if there is a transaction, the current transaction is supported, and if there is no one, it will be fine.
4.6 Configuring AOP facets
<aop:config> <!-- Configure which package classes to enter the transaction--> <aop:pointcut id="pointcut" expression="execution(* cn.it.shop.service.impl.*.*(..))" /> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/><!-- Connect the advice on the <span style="font-family:Microsoft YaHei;"> and the pointcut above --> <!-- aop:pointcut must be written on the aop:advisor, otherwise an error will be reported--> </aop:config>
AOP is the tangent-oriented programming. aop:pointcut defines a tangent. The configuration in the expression property means that all methods under the cn.it.shop.service.impl package, regardless of the return value and parameters, must enter the transaction. This package belongs to the dao layer and directly operates the database. aop:advice combines notifications and sections. We can directly use the advice and pointcut configured above to introduce them. After this configuration, it means that all methods under the cn.it.shop.service.impl package need to enter transaction management. Specifically, methods starting with save, update, and delete use the REQUIED method, and other methods use the SUPPORTS method. This makes it easy to understand the meaning of this configuration.
4.7 Test integration results
When we built the Hibernate environment before, we tested that we directly new a Service to operate the database, because it had not been integrated with Spring at that time. Now, after configuring beans.xml, Spring is allowed to manage Hibernate transactions. Therefore, the current test needs to hand over Service to Spring management, inject it through Spring, and rely on sessionFactory. If data can be inserted into the database, it means that the transaction is OK.
First, we need to match this service in Spring's configuration file beans.xml:
Copy the code as follows:<bean id="categoryService">
<property name="sessionFactory" ref="sessionFactory" /><!-- The dependency sessionFactory uses the sessionFactory we had previously equipped-->
</bean>
Secondly, we need to add a method to the CategoryService interface and its implementation class CategoryServiceImpl to test the integration situation:
public interface CategoryService { public void save(Category category); //Used to test the Hibernate environment public void update(Category category); //Used to test the integration of Spring and Hibernate} public class CategoryServiceImpl implements CategoryService { @Override //The situation where there is no integration with Spring public void save(Category category) { //Get session Session session = HibernateSessionFactory.getSession(); try { //Manual transaction session.getTransaction().begin(); //Execute business logic session.save(category); //Manually submit session.getTransaction().commit(); } catch(Exception e) { session.getTransaction().rollback(); throw new RuntimeException(e); } finally { HibernateSessionFactory.closeSession(); } } /*Spring and Hibernate the entire*/ private SessionFactory sessionFactory; //Define a sessionFactory //When you need to use sessionFactory, Spring will inject sessionFactory into public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } protected Session getSession() { //Get session from the current thread. If not, create a new session return sessionFactory.getCurrentSession(); } @Override //The situation after Spring and Hibernate is integrated public void update(Category category) { getSession().update(category); } } Now we can add test methods to the test class to test the results after the integration of Spring and Hibernate:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:beans.xml") public class SSHTest { @Resource private Date date; @Resource private CategoryService categoryService; @Test //Test Spring IOC development environment public void springIoc() { System.out.println(date); } @Test //Test //Test Hibernate's development environment, because there is no integration, you can directly new public void hihernate() { CategoryService categoryService = new CategoryServiceImpl(); Category category = new Category("Men's Casual", true); categoryService.save(category); } @Test //Test public void hibernateAndSpring() { categoryService.update(new Category(1, "Casual Women's", true)); //categoryService injected from above through Spring} }Then we checked the database and found that the category with id=1 has been modified into a casual woman's style, which means that the update was successful. At this point, Spring and Hibernate have been successfully integrated.
5. Build a Struts2 environment
5.1 Add corresponding configuration and jar packages
I put the jar package required for struts2 to run in the library of struts2.3.41, so I can just introduce it directly, and I won't repeat it. In addition, the web.xml file needs to be configured as follows:
<?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 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>
As above, we configured a StrutsPrepareAndExecuteFilter filter and set the url-pattern of the filter to *.action, that is, all .action suffixes will pass through this filter first, which is also the entrance to struts2.
5.2 Create Action and configure it into struts.xml file
We create an Action as follows:
public class CategoryAction extends ActionSupport { private CategoryService categoryService; //CategoryService is set to intuitively see the differences before and after integration with Spring public void setCategoryService(CategoryService categoryService) { this.categoryService = categoryService; } public String update() { System.out.println("----update----"); System.out.println(categoryService); //Output different return "index"; } public String save() { System.out.println("----save----"); System.out.println(categoryService);//Open different returns "index" before and after integration; } } Then we configure the struts.xml file, which is placed in the src directory:
<?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: Access update method--> <action name="category_*" method="{1}"> <result name="index">/index.jsp</result> </action> </package> </struts> 5.3 Testing the Struts2 environment
The test method is: write a jsp access Action, and if the Action can be created, it means that the struts2 environment is OK. That is, a series of processes in struts2 can be completed normally: jsp-->struts.xml-->Action-->struts.xml-->jsp, so that the environment of struts2 is ready. Let's write a simple 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> <!-- The following two ways of writing can be accessed--></span> <a href="${pageContext.request.contextPath }/category_update.action">Access update</a> <a href="category_save.action">Access save</a> </body> </html> Then we deploy the following project, open the tomcat server, enter: http://localhost:8080/E_shop/index.jsp in the browser, the normal jsp page will appear, then click two buttons, and still jump to index.jsp, and then we take a look at the output information of the console:
---update---
null
---save---
null
This shows that a line of struts2 has been completed and there is no problem with the environment. At this point, the struts2 development environment has been built.
We see that the console outputs null, which means that the categoryService is empty, which means that we have not got the categoryService at all, because we have not integrated with Spring and have not been injected, so null is normal. We flip up along the information output from the console and we will find a message: Choosing bean (struts) for (com.opensymphony.xwork2.ObjectFactory). The brackets say struts are generated by Struts2 before they are integrated with Spring.
6. Spring and Struts2 integration
6.1 Add the corresponding jar package
When Spring and Struts2 are integrated, the jar package is mainly in spring4.2.4-web, including struts2-spring-plugin-2.3.24.1.jar. The guide package will not be described again.
6.2 Leave Action and its dependencies to Spring Management
Configure Action and its dependencies in Spring's configuration file beans.xml. We currently have only one Action, which is configured as follows:
<bean id="date" /> <bean id="categoryAction" scope="prototype"> <property name="categoryService" ref="categoryService" /> <!-- The dependent categoryService is configured when integrating the above and Hibernate --> </bean>
6.3 Modify the configuration in struts.xml
It turns out that in struts.xml, the class attribute corresponds to the fully qualified name of the specific Action. Now change the value of the class attribute to the id value of the configured action in Spring, that is, categoryAction, as follows:
<struts> <package name="shop" extends="struts-default"> <!-- class corresponds to the id value of the Action configured in Spring, because it is to be handed over to Spring management--> <action name="category_*" method="{1}"> <result name="index">/index.jsp</result> </action> </package> </struts> 6.4 Configure the listener
Configure the listener ContextLoaderListener in web.xml so that Spring configuration files can be loaded when the server starts. as follows:
<?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 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> <!-- The listener's startup priority in web.xml is higher than that of filters, so it doesn't matter if it is matched below --> <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 Test integration results
We add a new statement to the Action to update the database, as follows:
public class CategoryAction extends ActionSupport { private Category category;//Set a private member variable to receive parameters brought by the URL. Note that the get and set methods should be written below. Private CategoryService categoryService; public void setCategoryService(CategoryService categoryService) { this.categoryService = categoryService; } public String update() { System.out.println("---update---"); System.out.println(categoryService);//Because it has been integrated with Spring, you can get this categoryService. It is not null after printing it out categoryService.update(category); //Add a new statement to update the database return "index"; } public String save() { System.out.println(categoryService); return "index"; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } } Then we modify the index.jsp file, as follows:
<%@ 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">Access update</a> <a href="category_save.action">Access save</a> </body> </html> Then we deploy the following project, open the tomcat server, enter: http://localhost:8080/E_shop/index.jsp in the browser, the normal jsp page will appear, then click the "Access update" button, and still jump to index.jsp, and then we take a look at the output information of the console:
---update--- cn.it.shop.service.impl.CategoryServiceImpl@7c5ecf80 Hibernate: update category set hot=?, type=? where id=?
We can output the information of the categoryService object, and can also output the SQL statement when executing the update statement. Then we query the database and find that the type of the data with id=2 has been updated to gga and hot has been updated to false. We flip the information output from the console upward and we will find a message: Choosing bean (spring) for (com.opensymphony.xwork2.ObjectFactory), with the brackets spring. Compared with the above situation, we can see that after Struts2 is integrated with Spring, the Action is handed over to Spring for management.
At this point, the integration of Struts2, Hibernate4 and Spring4 has been completed, and we can develop it in the SSH environment next!
The complete jar package required for SSH integration introduced in this article: Free download
The source code download address of the entire project: //www.VeVB.COM/article/86099.htm
Original address: http://blog.csdn.net/eson_15/article/details/51277324
(Note: At the end, the source code download of the entire project is provided! Everyone is welcome to collect or follow)
The above is the entire content of this article. I hope you can give you a reference and I hope you can support Wulin.com more.