**Spring-day01**
成恒 / chengheng@tedu.cn
## 框架
Spring, Spring MVC, Mybatis
## 什么是MVC
MVC:Model(模型), View(视图), Controller(控制器)
UserController <---> UserModel(UserService, UserDao)
模拟用户注册:
// Dao:Data Access Object,数据访问对象
// 一般指持久层,多半是操作数据库的
public class JdbcUserDao {
public void insert() {
// 将用户数据插入到数据表中
}
public boolean checkUsernameExists(String username) {
// 检查用户名是否被注册
}
}
// Service:业务逻辑,如何保障数据按照设计者制定的规则来访问
public class UserService {
private JdbcUserDao dao = new JdbcUserDao();
// 注册
public void reg() {
if (dao.checkUsernameExists() == true) {
// 注册失败,用户名被占用
} else {
dao.insert(); // 调用Dao完成插入数据
}
}
}
如果`JdbcUserDao`需要被替换为以下`MybatisUserDao`:
public class MybatisUserDao {
public void insert() {
// 将用户数据插入到数据表中
}
public boolean checkUsernameExists(String username) {
// 检查用户名是否被注册
}
}
则,`UserService`就必须调整:
public class UserService {
private MybatisUserDao dao = new MybatisUserDao();
// ... 其它可以不变
}
但是,如果一个项目中,曾经在许多代码中都出现了`private JdbcUserDao dao = new JdbcUserDao();`,那么,在替换过程中,就需要执行多次的替换!
所以,可以先设计一个`Dao`的接口,例如:
public interface IUserDao {
void insert();
boolean checkUsernameExists(String username);
}
则`JdbcUserDao`和`MybatisUserDao`都应该也都可以实现这个接口,基于多态的使用原则,原来的
private JdbcUserDao dao = new JdbcUserDao();
可以调整为:
private IUserDao dao = new JdbcUserDao();
则后续需要更换`JdbcUserDao`时,就可以少替换一半的代码!
然后,各个`IUserDao`的实现类,我们希望不由他人(指程序员)随意创建,而是基于特定的方式获取:
public class UserDaoFactory {
public IUserDao getInstance() {
return new MybatisUserDao();
}
}
则以上代码可以进一步优化为:
private IUserDao dao = new UserDaoFactory().getInstance();
在整个项目中,需要用上`User`相关的持久层对象时,都使用以上语法即可,即使后续需要替换为一个新的实现类,也只需要修改`UserDaoFactory`类中的方法的返回值即可,只需修改这1处!!!
最后,一般不会因为创建1个对象,就创建1个工厂,所以会调整为:
public abstract class UserDaoFactory {
public static IUserDao getInstance() {
return new MybatisUserDao();
}
}
当需要对象时:
private IUserDao dao = UserDaoFactory.getInstance();
## Spring
Spring框架主要解决了创建类的对象和管理类的对象的问题,使用Spring框架后,可以不必再使用`new`语法创建对象。
Spring也称之为“Spring容器”,存放着由它创建出来的所有对象,当开发过程中需要某个对象时,直接从容器中获取即可!
### 1. 第1次使用Spring
1 创建`Maven Project`,创建过程中勾选`Create a simple project`,填写`Group Id`和`Artifact Id`,前者可以使用项目的包名,后者使用项目的名称(显示在Eclipse左侧的项目名),然后把`packaging`选为`war`项目,然后自动生成`web.xml`。
2 从FTP下载`application.zip`文件,并解压,将得到的xml文件重命名为`applicationContext.xml`(非必要操作),将文件复制到项目的`src\main\resources`下。
3 在`pom.xml`中添加对`spring-webmvc`的依赖(注意区分大小写),使用的版本只需要是`3.2`或以上版本均可(通常不推荐使用`5.x`版本),当Maven更新成功后,会在项目的库的Maven分支下显示8个jar包。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
</dependencies>
4 创建测试用的类型,例如创建`cn.tedu.spring.entity.User`类,内容可以为空。
public class User {
}
5 创建测试运行的类,例如`cn.tedu.spring.test.Test`类,创建时,勾选中`main`方法,例如:
public class Test {
public static void main(String[] args) {
// 传统做法:直接new对象即可(只要存在默认构造方法)
User user = new User();
// 测试输出
System.out.println(user);
}
}
6 如果希望由Spring来创建User对象,并由Spring管理,首先,得让Spring知道它应该创建和管理谁,则需要编辑`applicationContext.xml`文件,在文件中添加配置:
<!-- id:自定义名称,不可冲突,后续将根据这个名称从Spring容器中获取对象,通常使用类名且首字母小写作为id -->
<!-- class:需要Spring创建并管理的类 -->
<bean
id="user"
class="cn.tedu.spring.entity.User">
</bean>
7 在测试类(`Test.java`)的`main()`方法中:
// 加载Spring配置文件,并获取Spring容器(名为ac的变量)
ApplicationContext ac
= new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 从Spring容器中获取所需的对象
// getBean()方法的参数是XML中配置的id
User user = (User) ac.getBean("user");
// 测试输出
System.out.println(user);
### 2. 使用Spring管理对象的配置方式
#### 2.1. [重要] 需要管理的类存在无参数的构造方法
例如以上的`User`类型,没有写构造方法,即使用默认构造,也就是公有的、无参数的构造方法,适用于:
<bean
id="user"
class="cn.tedu.spring.entity.User" />
满足这种使用方式的类,必须存在无参数的构造方法,至于有没有其它构造方法,或者构造方法使用的是哪种访问权限,这些是没有要求的!
#### 2.2. [了解] 需要管理的类存在静态工厂方法
以`Calendar`为例,存在`public static Calendar getInstance()`方法,这个方法就是`Calendar`类的静态工厂方法。
满足这种条件的类的配置方式:
<!-- factory-method:工厂方法的名称 -->
<bean
id="calendar"
class="java.util.Calendar"
factory-method="getInstance" />
#### 2.3. [了解] 需要管理的类存在实例工厂方法
假设存在以下2个类:
public class Phone {
public Phone(String name) {
}
}
public class PhoneFactory {
public Phone getInstance() {
return new Phone();
}
}
由于`PhoneFactory`中的`getInstance()`方法没有使用`static`关键字,如果要调用这个方法,必须先创建`PhoneFactory`类的对象(实例),所以,这种情况就是满足实例工厂方法的。
对于这种情况,在Spring配置文件中,需要做2项配置:
<!-- factory-bean:可以调用实例工厂方法的对象 -->
<bean
id="phone"
class="cn.tedu.spring.entity.Phone"
factory-bean="phoneFactory"
factory-method="getInstance" />
<bean
id="phoneFactory"
class="cn.tedu.spring.entity.PhoneFactory" />
#### 小结
以上介绍的3种方式中,仅第1种实用性较好,后面的2种仅了解即可。
### 由Spring管理的对象的生命周期
Spring管理的对象,会在加载Spring配置文件,获取Spring容器时,就已经创建对象!
由Spring管理的对象,默认是单例的!从生命周期来看,默认是常驻内存的!
在`<bean>`节点中,有名为`scope`的属性,常用取值有`singleton`(单例,默认值),`prototype`(非单例)………………
如果由Spring管理的对象是单例,默认情况下是饿汉式的,通过配置`lazy-init`属性可以设置是否懒加载,如果需要是懒汉式,则配置该属性且值为`true`。
如果由Spring管理的对象是单例,还可以配置2个生命周期方法,分别是`init-method`和`destroy-method`,分别用于表示初始化方法和销毁方法,初始化方法的执行时间取决于是否单例和是否懒加载(非单例时,每次获取对象时执行;懒汉式单例时,第1次获取对象时执行;饿汉式单例时,加载Spring配置文件时执行),而销毁方法会在关闭Spring容器(调用`ac.close()`方法)时被调用。
注意:如果由Spring管理的对象不是单例的,其初始化的生命周期方法也是可以执行,但是,没有“只执行1次”的特性,不便于控制,所以,一般情况下,非单例的类型,并不配置生命周期方法。
## 其它
## 1. 生命周期
生命周期描述的是某个事务从无到有,再到无的历程,通常讨论生命周期问题,关注的可能是中间的历程,或者整个生命周期存在的持续时长。
以`Servlet`为例,关注其生命周期具体表现为了解一系列的方法,例如:
init() service() doGet()/doPost() destory()
那么,学习`Servlet`的生命周期的主要意义在于了解:有哪些生命周期方法,它们会在什么时候被调用,进而,我们才知道应该重写哪些方法,并且哪些方法中编写什么样的代码!
## 2. 如果从Maven服务器下载的jar包有问题
1 确定本地jar包的位置
在Eclipse的设置中,找到`Maven` -> `User Settings`,在右侧找到`Local Repository`,对应的路径值,就是本地jar的文件夹,通常名为`m2`或`.m2`
2 关闭Eclipse
3 删除本地jar包的文件夹
4 重新打开Eclipse
5 对项目点右键,选择`Maven` -> `Update Project`,并在弹出的窗口中勾选`Force ...`选项,强制更新
## 3. 内存
专业领域中的描述,内存指的是内部存储器,主要包括:RAM / Cache / ROM。
RAM = Random Access Memory,具体的表现就是内存条,也是手机行业中描述的“运行内存”。
RAM是CPU可以直接交互的唯一硬件!也是CPU与其它硬件交换数据的桥梁!
RAM一旦断电,所有数据会消失!
正在执行的程序与数据都在RAM中!
### 4. static关键字
`static`关键字表达为“静态的”,具体的特性是:唯一,常驻。
例如:
public class Something {
public static String name="不知道";
}
然后:
Something s1 = new Something();
Something s2 = new Something();
s1.name = "hello";
并且:
System.out.println(s1.name);
System.out.println(s2.name);
所以,被`static`修饰的成员具有唯一的特性,在访问时,也不应该通过对象去访问,因为它不归属于任何一个对象,而是应该通过类的名称去引用,例如`Something.name`。
而常驻,表示它常驻内存!因为正在执行的程序和数据必须在内存中,所以:
public class Test {
private int i = 10;
public static void main(String[] args) {
System.out.println(i);
}
}
以上代码是错误的,不可以在`main()`方法中直接使用`i`变量,因为`main()`方法被`static`修饰,就是常驻内存的,而`i`没有使用`static`修饰,默认并不存在于内存中,所以无法直接访问!也就是通常表现的规则:`static`成员不可以直接访问非`static`成员!
如果在`main()`方法中一定要使用到这个`i`,可以为`i`添加`static`修饰,或者,在`main()`方法中,先`new`这个类的对象,例如`Test test = new Test();`,然后通过`new`出来的对象去引用`i`,例如`System.out.println(test.i);`也是可以的!
### 5. 单例模式
单例模式实现某个类在同一时间只允许有1个实例,不可能同时存在多个实例。
单例模式分为饿汉式单例和懒汉式单例。
饿汉式的单例模式表现为:
public class King {
private static King king = new King();
private King() {
}
public static King getInstance() {
return king;
}
}
所以,饿汉式的单例模式是在一开始就把对象创建好了,任何时候需要时,都可以随时使用!
相反,懒汉式的单例模式是一开始并没有创建对象,仅当需要时,才完成第1次创建:
public class King {
private static King king;
private King() {
}
public static King getInstance() {
if (king == null) {
king = new King();
}
return king;
}
}
懒汉式的单例也称之为:延迟加载。
注:以上代码的饿汉式单例是不完整的,没有解决线程安全问题。
**Spring-day02**
## 1 注入属性的值(重要)
### 1.1 基本概念
以某个`User`类为例:
public class User {
public String name;
}
如果在Spring的配置文件中进行配置,可以使得加载Spring配置文件时,就创建出它的对象,在此基础之上,还可以通过配置,使得该类中的`name`属性(也可以是其它属性)具有值,最终,在程序运行时,如果获取`User`的对象,其中的`name`属性是已经被赋值的!
### 1.2 通过SET方法注入(重要)
首先,需要为类中的属性(需要被注入的值属性)添加SET方法,可以通过Eclipse工具自动生成属性的SET方法,例如:
public class User {
public String name;
public void setName(String name) {
this.name = name;
}
}
然后,在Spring的配置文件中,将`<bean>`节点写成成对的标签,并添加`<property>`子节点:
<!-- property节点:用于注入属性的值 -->
<!-- name:属性名 -->
<!-- value:属性值 -->
<bean id="user"
class="cn.tedu.spring.entity.User">
<property name="name" value="David" />
</bean>
如果有多个属性需要注入值,则每个属性都需要有SET方法,并且,在`<bean>`下使用多个`<property>`节点进行配置。
注意:在配置`<property>`节点时,其中的`name`属性用于指定属性名,其实,需要指定的是SET方法的名称中除了`set`部分以外的字符,例如在类中的属性名叫`age`,而SET方法的名称叫`setUserAge`,那么,在配置时,需要配置为`name="userAge"`。也就是说,Spring在工作时,会根据配置文件中的例如`userAge`名称,将首字母改为大写,并在左侧拼上`set`,得到`setUserAge`作为方法名称,然后调用方法,完成值的注入!不过,这个问题可以不用过多关注,只要保证每个SET/GET方法都是Eclipse这些开发工具生成的名称即可,因为这些工作生成SET/GET方法时也是使用这样的规则!这样的话,就把`name`属性视为设置的是属性名也可以!
以上做法适用于属性的类型是基本值(基本值:可以直接通过键盘输入的,例如字符串、数值等)的,如果某个属性的值不是基本值可以描述的,例如:
public class User {
public Date regTime;
}
当添加了SET方法以后,在Spring的配置文件中,需要先配置出这个属性值的`<bean>`,然后 ,在注入值时,在`<property>`节点中使用`ref`属性进行配置:
<bean id="user"
class="cn.tedu.spring.entity.User">
<property name="regTime" ref="date" />
</bean>
<bean id="date"
class="java.util.Date" />
### 1.3 通过构造方法注入(不常用)
如果属性的值是通过构造方法注入的,则需要有带参数的构造方法,且不要求有SET方法,例如:
public class Person {
public String name;
public Person(String name) {
this.name = name;
}
}
然后,在Spring的配置文件中:
<!-- constructor-arg节点:配置构造方法参数 -->
<!-- index:参数索引,从0开始顺序编号 -->
<!-- value:参数值 -->
<bean id="person"
class="cn.tedu.spring.entity.Person">
<constructor-arg
index="0"
value="Jackson" />
</bean>
在配置`<constructor-arg>`节点时,`index`属性表示某个构造方法参数的索引,从0开始顺序编号。
与通过SET方式注入相同,如果是基本值,通过`value`属性来配置,如果是其它对象类型,通过`ref`属性来引用另一个bean的id。
当有了这种方式后,无论某个类有没有默认构造方法(无参数的构造方法),都可以通过Spring的配置后,由Spring创建并管理对象。
### 1.4 实际应用
在实际应用中,需要注入值的,往往是功能相关的类,并不是实体类,例如:
public class UserDao {
public void insert(User user) {
}
}
public class UserService {
public UserDao userDao;
public void reg(User user) {
userDao.insert(user);
}
}
在以上代码中,`UserService`中需要`UserDao`的对象,则可以通过Spring的配置,使得`UserService`被创建出来时,就会为`UserDao userDao`这个属性注入值。
在实际应用中,更多的会使用接口来编程,例如先创建持久层接口,并约定抽象方法:
public interface IUserDao {
void insert();
}
然后,使得原有的`UserDao`类实现以上接口:
public class UserDao implements IUserDao {
...
最后,在`UserDao`的调用者,也就是在`UserService`类中,将声明和参数类型全部换成接口类型:
public class UserService {
private IUserDao userDao; // 上午写的时候,属性类型是UserDao
// 上午写的时候,参数类型是UserDao
public void setUserDao(IUserDao userDao) {
...
}
经过这样的调整后,可以发现,在`UserService`这个类中,完全不体现是哪个类实现的`IUserDao`接口,从而使得`UserService`这个类与`UserDao`类没有太直接的关系,进而,在后续的开发中,如果`UserDao`不满足需求,需要另外写一个例如`UserDaoImpl`类来工作,也只需要让新的`UserDaoImpl`也实现`IUserDao`接口,并在Spring的配置文件中,把原来配置的`UserDao`换成`UserDaoImpl`即可,而`UserService`里不需要有任何调整,所以,总的来说,这种做法的好处就是“哪里有问题改哪里”。
### 1.5 自动装配
在Spring中“自动装配”表示自动的为所有属性注入值,而不需要在Spring的配置文件中去配置各个`<property>`节点!取而代之的是在对应的`<bean>`节点中配置`autowire`属性,例如:
<bean id="userService"
class="cn.tedu.spring.dao.UserDaoImpl"></bean>
<bean id="userService"
class="cn.tedu.spring.service.UserService"
autowire="byType">
</bean>
<bean id="userController"
class="cn.tedu.spring.controller.UserController"
autowire="byName" />
关于`autowire`属性,常用取值有`byName`和`byType`。
当取值为`byName`的时候,表示**按照名称实现自动装配**,例如,有属性`private IUserService userService;`时,Spring将在容器中尝试找bean id为`userService`的对象,如果找到,则自动装配,**也就是说:属性的名称,与Spring管理的范围内的某个bean id是匹配的(本质上,还是SET方法中的名称需要与bean id是匹配的)!**当然,本质上还是通过SET方式注入的,所以,这个属性还是需要有SET方法。
当取值为`byType`的时候,表示**按照类型实现自动装配**,例如,有属性`private IUserService userSerivce;`时,Spring不再关注任何bean id,而是在Spring管理的范围查找有没有类型与`IUserService`匹配的对象,如果找到,则自动装配,**也就是说:属性的类型,与Spring管理的范围内的某个bean的类型是匹配的!**,需要注意的是:使用这种自动装配方式,必须保证匹配的类型只有1个,如果超过1个,则无法自动装配,程序会抛出异常!
其实,规范的程序中并不存在命名的相关问题,所以,使用`byName`肯定是最稳妥的做法!事实上,也几乎不会出现同一个接口有2个或更多个实现类同时共存的情况!也就不会出现`byType`的冲突问题!所以,用哪种其实无所谓!
另外,该属性还有其它取值,暂时不关注。
自动装配的特性是:能自动装配就会为对应的属性注入值,对于那些无法自动装配属性,则不处理!
由于类中的属性哪些能被装配值,又有哪些无法被装配值,无论是从Spring的配置文件中,还是Java源代码中,都是没有明确的表现的,特别是在完成项目时,可能涉及的类有很多,更加难以理解代码的运行,所以,不推荐在项目中使用这种做法完成值的注入!
尽管不推荐,但是,`byType`和`byName`的装配思想是需要理解的,后面用得着!
### 1.6 注入集合类型的值
使用Spring注入值时,还可以为List、Set、Map等集合类型的属性注入值!注入方式可以是:
<!-- // Jack, Rose, LiLei, HanMM
public List<String> names; -->
<property name="names">
<list>
<value>Jack</value>
<value>Rose</value>
<value>LiLei</value>
<value>HanMM</value>
</list>
</property>
<!-- // Beijing, Shanghai, Guangzhou, Shenzhen
public Set<String> cities; -->
<property name="cities">
<set>
<value>Beijing</value>
<value>Shanghai</value>
<value>Guangzhou</value>
<value>Shenzhen</value>
</set>
</property>
<!-- // username=admin, password=123456, from=Hangzhou
public Map<String, String> session; -->
<property name="session">
<map>
<entry key="username" value="admin" />
<entry key="password" value="123456" />
<entry key="from" value="Hangzhou" />
</map>
</property>
也可以是:
<bean id="collectionEntity"
class="cn.tedu.spring.entity.CollectionEntity">
<property name="names" ref="n" />
</bean>
<util:list id="n">
<value>Tom</value>
<value>Jerry</value>
</util:list>
由于一般很少直接在Spring的配置文件中确定一些值,所以,以上这些做法的实用性并不高!
关于`Properties`类型的配置可以是:
<!-- // 数据库配置信息
public Properties dbConfig; -->
<property name="dbConfig">
<props>
<prop key="username">root</prop>
<prop key="password">root</prop>
<prop key="initActive">2</prop>
<prop key="maxSize">10</prop>
</props>
</property>
但是,这种做法并不好,因为,把数据库的配置写在XML文件中,如果客户修改,却不具备专业知识,可能破坏XML文件的格式,导致整个文件无法使用!所以,此类的配置通常会写在`*.properties`文件中,然后从这些文件中再读取:
db.properties
url=jdbc:mysql://localhost:3306/db?characterEncoding=utf8
driver=com.mysql.jdbc.Driver
username=root
password=root
initActive=2
maxSize=10
然后,在Spring的配置文件中添加一个与`<bean>`同级的节点:
<util:properties id="dbConfig"
location="classpath:db.properties"></util:properties>
当Spring加载这个配置文件时,就会直接解析`db.properties`文件,并且,这个`<util:properties>`就是一个`Properties`类型的对象,所以,当需要注入值时,使用`ref`属性引用到这个`<util.properties>`的`id`即可:
<!-- // 数据库配置信息
public Properties dbConfig; -->
<property name="dbConfig" ref="dbConfig" />
这种做法,简单,实用,需要掌握!
## 其它
### 1 Eclipse中的常用快捷键
Ctrl + Shift + F 格式化源代码
Ctrl + Shift + O 整理package
Alt + 上/下 向上/下移动光标所在行的代码,可以是多行
Ctrl + Alt + 上/下 将光标所在行的代码向上/下复制,可以是多行
Ctrl + D 删除光标所在行的代码,可以是多行
Ctrl + 2, R 批量重命名代码中的某个名称,可以是类名、方法名、量的名称,操作之前需先选中
### 2 代码中的命名规范
### 3 MVC中的访问流程
控制器层(Controller) <----> 业务层(Service) <----> 持久层(Dao)