# Spring基础
# Spring是什么
Spring是分层的Java SE/EE应用全栈轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核。它提供了展现层Spring MVC和持久层Spring JDBCTemplate以及业务层事务管理等众多企业级应用技术,还能整合开源世界及众多第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
Spring的体系结构如下:
# Spring历史
- 1997 年,IBM提出了EJB 的思想
- 1998 年,SUN制定开发标准规范 EJB1.0
- 1999 年,EJB1.1 发布
- 2001 年,EJB2.0 发布
- 2003 年,EJB2.1 发布
- 2006 年,EJB3.0 发布
2002年Rod Johnson根据多年经验撰写的《Expert o-ne-on-One J2EE Design and Development》。其中对正统J2EE架构的臃肿、低效的质疑,引发了人们对正统J2EE的反思。这本书也体现了Rod Johnson对技术的态度,技术的选择应该基于实证或是自身的经验,而不是任何形式的偶像崇拜或者门户之见。正是这本书真正地改变了Java世界。基于这本书的代码,Rod Johnson创建了轻量级的容器Spring。Spring的出现,使得正统J2EE架构一统天下的局面被打破。基于Struts+Hibernate+Spring的J2EE架构也逐渐得到人们的认可,甚至在大型的项目架构中也逐渐开始应用。
Rod Johnson的新作《Expert o-ne-on-one J2EE Development without JEB》则更让人吃惊,单单“Without EJB”一词就会让大多数J2EE架构师大跌眼镜了。不过Rod Johnson可能仅仅是想通过“Without EJB”一词表明应该放开门户之见。这也是Rod Johnson一贯的作风。也许正是这种思想,促使Rod Johnson创建了Spring,真正改变了Java世界。
# Spring优势
- 方便解耦,简化开发,通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
- AOP 编程的支持,通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
- 声明式事务的支持,可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
- 方便程序的测试,可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
- 方便集成各种优秀框架,Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。
- 降低 JavaEE API 的使用难度,Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
# Spring快速入门
原来我们直接创建对象,现在要通过Spring框架来创建,读取xml配置文件,根据id标识获得Bean全限定名,通过反射创建Bean对象:
Spring程序的开发步骤如下:
导入 Spring 开发的基本包坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.9.RELEASE</version> </dependency>
1
2
3
4
5编写 Dao 接口和实现类
UserDao.java:
package com.muyun.dao; public interface UserDao { public void Save(); }
1
2
3
4
5UserDaoImpl.java:
package com.muyun.dao.impl; import com.muyun.dao.UserDao; public class UserDaoImpl implements UserDao { public UserDaoImpl() { System.out.println("UserDaoImpl created..."); } }
1
2
3
4
5
6
7
8
9
10创建 Spring 核心配置文件applicationContext.xml为:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> </beans>
1
2
3
4
5
6
7
8
9
10
11
12在 Spring 配置文件中配置 UserDaoImpl:
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
1使用 Spring 的 API 获得 Bean 实例
package com.muyun.demo; import com.muyun.dao.UserDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserDaoDemo { public static void main(String[] args) { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); userDao.Save(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Spring配置文件
# Bean创建
使用Spring框架后,我们不用再自己new对象,而是交给Spring来创建,但是Spring需要配置,这个时候需要用到Bean标签,默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。它包含:
- id:Bean实例在Spring容器中的唯一标识
- class:要实例化的Bean的全限定名称
- scope属性:Bean的作用范围,常用是Singleton(默认)和prototype
- property标签,用于属性注入,它包含如下:
- name属性:属性名称
- value属性:注入的普通属性值
- ref属性:注入的对象引用值
- list标签
- map标签
- props标签
- constructor-arg标签,它包含如下:
- name属性:构造函数参数名
- ref:引用的对象bean的id
- import标签:导入其他的Spring的子配置文件
它的样例如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- <bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy">
<property name="username" value="li"></property>
<property name="age" value="18"></property>
</bean> -->
<bean id="user1" class="com.muyun.domain.User">
<property name="name" value="xie"></property>
<property name="addr" value="beijing"></property>
</bean>
<bean id="user2" class="com.muyun.domain.User">
<property name="name" value="li"></property>
<property name="addr" value="tianjin"></property>
</bean>
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy">
<property name="strList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="userMap">
<map>
<entry key="u1" value-ref="user1"></entry>
<entry key="u2" value-ref="user2"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">p1...</prop>
<prop key="p2">p2...</prop>
<prop key="p3">p3...</prop>
</props>
</property>
</bean>
<!-- <bean id="userDao" class="com.muyun.factory.StaticFactory" factory-method="getUserDao"></bean> -->
<!-- <bean id="factory" class="com.muyun.factory.DynamicFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean> -->
<!-- <bean id="userService" class="com.muyun.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
-->
<!-- <bean id="userService" class="com.muyun.service.impl.UserServiceImpl" p:userDao-ref="userDao" /> -->
<bean id="userService" class="com.muyun.service.impl.UserServiceImpl">
<constructor-arg name="myUserDao" ref="userDao"></constructor-arg>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Bean作用域
上面scope指对象的作用范围,取值如下:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
session | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当于 session |
注意:
- 当scope的取值为singleton时
- Bean的实例化个数:1个
- Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
- Bean的生命周期:
- 对象创建:当应用加载,创建容器时,对象就被创建了
- 对象运行:只要容器在,对象一直活着
- 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
- 当scope的取值为prototype时
- Bean的实例化个数:多个
- Bean的实例化时机:当调用getBean()方法时实例化Bean
- 对象创建:当使用对象时,创建新的对象实例
- 对象运行:只要对象在使用中,就一直活着
- 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
# Bean生命周期
Bean还可以配置类初始化和类销毁方法:
- init-method:指定类中的初始化方法名称
- destroy-method:指定类中销毁方法名称
初始化方法销毁方法的类为:
package com.muyun.dao.impl;
import com.muyun.dao.UserDao;
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("UserDaoImpl created...");
}
public void init() {
System.out.println("初始化方法......");
}
public void destroy() {
System.out.println("销毁方法......");
}
public void Save() {
// TODO Auto-generated method stub
System.out.println("save running!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
配置文件如下:
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
# Bean实例化
Bean实例化有三种方式:
- 无参构造方法实例化
- 工厂静态方法实例化
- 工厂实例方法实例化
上面均为无参构造方法实例化,下面主要说后两种:
# 工厂静态方法实例化
工厂类为:
package com.muyun.factory;
import com.muyun.dao.UserDao;
import com.muyun.dao.impl.UserDaoImpl;
public class StaticFactory {
public static UserDao getUserDao() {
return new UserDaoImpl();
}
}
2
3
4
5
6
7
8
9
10
配置文件为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="userDao" class="com.muyun.factory.StaticFactory" factory-method="getUserDao"></bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
# 工厂实例方法实例化
工厂类为:
package com.muyun.factory;
import com.muyun.dao.UserDao;
import com.muyun.dao.impl.UserDaoImpl;
public class DynamicFactory {
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
2
3
4
5
6
7
8
9
10
配置文件为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="factory" class="com.muyun.factory.DynamicFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 依赖注入
依赖注入(Dependency Injection)概念离不开控制反转(IoC,Inversion of Control),在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除,比如业务层仍会调用持久层的方法,这种依赖关系在使用Spring之后会让Spring来维护,我们可以把持久层对象传入业务层,不用再自己获取。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。更形象地说是IoC像是我们去买东西,由我们自己拿东西到柜台结算变成了我们告诉服务员要什么,让服务员去拿给我们。
控制反转包括依赖注入和依赖查找,依赖注入(DI)是组件之间依赖关系由容器在运行期决定,形象地说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需修改任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是"谁依赖谁,为什么需要依赖,谁注入谁,注入了什么":
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据);
本质上IoC和DI是同一思想下不同维度的表现,用通俗的话说就是,IoC是bean的注册,DI是bean的初始化。
# 注入方式
在Spring中依赖注入有两种方式:
- set方法
- 构造方法
# set方法
UserServiceImpl中添加属性及set方法:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
...
}
2
3
4
5
6
7
applicationContext.xml文件中:
<beans>
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
<bean id="userService" class="com.muyun.service.impl.UserServiceImpl">
<!-- name为类set方法后的名字,首字母小写;ref为bean的id,即引用的对象 -->
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
2
3
4
5
6
7
8
set方法有一种比较简单的使用方法,即p命名空间。applicationContext.xml修改为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="userService" class="com.muyun.service.impl.UserServiceImpl" p:userDao-ref="userDao" />
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
# 构造方法
构造方法和set方法类似,UserServiceImpl中添加构造方法:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao myUserDao) {
this.userDao = myUserDao;
}
public UserServiceImpl() {
}
...
}
2
3
4
5
6
7
8
9
10
applicationContext.xml文件中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
<bean id="userService" class="com.muyun.service.impl.UserServiceImpl">
<!-- name为构造方法参数的名字;ref为bean的id,即引用的对象 -->
<constructor-arg name="myUserDao" ref="userDao"></constructor-arg>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 注入数据类型
上面注入的都是引用Bean,除了对象的引用可注入外,还有下面数据类型可注入:
- 普通数据类型
- 引用数据类型
- 集合数据类型
# 普通数据类型
UserDaoImpl.java内容:
public class UserDaoImpl implements UserDao {
private String username;
private int age;
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @param age the age to set
*/
public void setAge(int age) {
this.age = age;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
applicationContext.xml文件中:
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy">
<property name="username" value="li"></property>
<property name="age" value="18"></property>
</bean>
2
3
4
# 引用数据类型和集合类型
创建一个新类User.java
package com.muyun.domain;
public class User {
private String name;
private String addr;
/**
* @return String return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return String return the addr
*/
public String getAddr() {
return addr;
}
/**
* @param addr the addr to set
*/
public void setAddr(String addr) {
this.addr = addr;
}
public String toString() {
return "name:" + name + ";" + "addr:" + addr;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
UserDaoImpl.java内容为:
public class UserDaoImpl implements UserDao {
private List<String> strList;
private Map<String, User> userMap;
private Properties properties;
/**
* @param strList the strList to set
*/
public void setStrList(List<String> strList) {
this.strList = strList;
}
/**
* @param userMap the userMap to set
*/
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
/**
* @param properties the properties to set
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
applicationContext.xml文件中:
<bean id="user1" class="com.muyun.domain.User">
<property name="name" value="xie"></property>
<property name="addr" value="beijing"></property>
</bean>
<bean id="user2" class="com.muyun.domain.User">
<property name="name" value="li"></property>
<property name="addr" value="tianjin"></property>
</bean>
<bean id="userDao" class="com.muyun.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy">
<property name="strList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="userMap">
<map>
<entry key="u1" value-ref="user1"></entry>
<entry key="u2" value-ref="user2"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">p1...</prop>
<prop key="p2">p2...</prop>
<prop key="p3">p3...</prop>
</props>
</property>
</bean>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Spring子文件配置
实际开发中,Spring的配置内容非常多,这会导致Spring配置很繁杂且体积很大,这时候可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载:
<import resource="applicationContext-xxx.xml"/>
# ApplicationContext
ApplicationContext为接口类型,代表应用上下文,可以通过其实例获得Spring容器中的Bean对象,它的继承体系如下:
它有三个实现类:
- ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件推荐使用这种
- FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类来创建 spring 容器,用来读取注解。
使用方式如下:
public class UserController {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// ApplicationContext app = new FileSystemXmlApplicationContext("X:\\work\\project\\JavaFrameworkLearn\\SpringBase\\spring-ioc\\src\\main\\resources\\applicationContext.xml");
UserService userService = (UserService) app.getBean("userService");
userService.save();
}
}
2
3
4
5
6
7
8
9
注意getBean有两种形式:
package com.muyun.demo;
import com.muyun.service.UserService;
import com.muyun.service.impl.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class UserController {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// UserService userService = (UserService) app.getBean("userService");
// 当有多个service时,使用上面比较合适
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19