Spring 入门笔记 (一)

Spring 框架是由于软件开发的复杂性而创建的。Spring 使用的是基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。然而,Spring 的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分 Java 应用都可以从 Spring 中受益!

Spring 简述

Spring 的一个最大的目的就是使 JAVA EE 开发更加容易。同时,Spring 之所以与 Struts、Hibernate 等单层框架不同,是因为 Spring 致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说 Spring 是一个提供了更完善开发环境的一个框架,可以为 POJO (Plain Old Java Object) 对象提供企业级的服务

在 Spring 刚出生的时候,主要的负责两件事情:IOC、AOP
IOC(Inversion of Control),即 “控制反转”,为什么需要控制反转?谁控制谁?为啥叫做反转?IOC 究竟是做什么的?
AOP 以后再说,先从 IOC 容器开始,下图是 Spring 的几大模块:
mark
Spring DAO:Spring 提供了对 JDBC 的操作支持:JdbcTemplate 模板工具类 。

Spring ORM:Spring 可以与 ORM 框架整合。例如 Spring 整合 Hibernate 框架,其中 Spring 还提供 HibernateDaoSupport 工具类,简化了 Hibernate 的操作 。

Spring WEB:Spring 提供了对 Struts、Springmvc 的支持,支持 WEB 开发。与此同时 Spring 自身也提供了基于 MVC 的解决方案 。

Spring AOP:Spring 提供面向切面的编程,可以给某一层提供事务管理,例如在 Service 层添加事物控制 。

Spring JEE:J2EE 开发规范的支持,例如 EJB 。

Spring Core:提供 IOC 容器对象的创建和处理依赖对象关系 。

Spring 的 helloworld

Spring 的环境搭建

首先需要说明的是:Spring 不仅仅是 JavaEE 才能用的框架,而是 SE 同样适用的,所以为了方便起见,我直接新建 Java 工程来完成 Spring 的 HelloWorld,但是 Spring 需要的 jar 包,所以我直接使用 Maven 工程来演示,假设你不用 maven 或者想直接使用普通的 Java 工程来完成 Spring 的 HelloWorld,那么 点击这里 在这里可以下载到 Spring 核心包的源码,Jar 包以及说明文档。

eclipse 如何集成 Spring 相关的工具包在此不再赘述,我直接使用的是 Spring 官方集成的工具,也是 eclipse,但是你可以叫做 STS,下载地址在这里

1
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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xpu.maven</groupId>
<artifactId>SpringDemo_01</artifactId>
<version>0.0.1-SNAPSHOT</version>

<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</project>

以上是 Spring 的 HelloWorld 所需的 Jar 包,接下来解释一下这些 Jar 的作用:
mark
spring-core 是 Spring 的核心工具包
spring-cji 是核心包所需的依赖包,
spring-aop:Spring 的面向切面编程,提供 AOP(面向切面编程)的实现
spring-beans:Spring IOC 的基础实现,包含访问配置文件、创建和管理 bean 等
spring-context:在基础 IOC 功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI 定位,EJB 集成、远程访问、缓存以及多种视图层框架的支持
spring-expression:spring 表达式语言,就像 EL 表达式一样的东西
commons-logging:这个是只是一个日志包,不用理会

Spring 的 HelloWorld

现有一个 JavaBean 是 Students 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.xpu.bean;
public class Students {
private int stuNo;
private String stuName;
private int stuAge;
public Students() {
super();
}
public Students(int stuNo, String stuName, int stuAge) {
super();
this.stuNo = stuNo;
this.stuName = stuName;
this.stuAge = stuAge;
}
public int getStuNo() {...}
public void setStuNo(int stuNo) {...}
public String getStuName() {...}
public void setStuName(String stuName) {...}
public int getStuAge() {...}
public void setStuAge(int stuAge) {...}
@Override
public String toString() {...}
}

如果我们需要拿到这个 Students 类的对象的话其实很简单:
1
Students stu = new Students(1, "Tim", 20);

接下来我们使用 IOC 的方式来获取到 Students 的对象,在 src(如果是 maven 工程的话直接在 resource 下新建即可)下新建一个 applicationContext.xml 文件,如果你已经安装了插件或者直接使用 STS 的话,会自动生成一些内容:
mark

1
2
3
4
5
6
7
<!-- 该文件中产生的所有对象,被 Spring 放入了一个称之为 springIOC 容器的地方 -->
<!-- id:唯一标识符 class:JavaBean 的 class 对象 -->
<bean class="com.xpu.bean.Students" id="students">
<property name="stuNo" value="1"></property>
<property name="stuName" value="Tim"></property>
<property name="stuAge" value="20"></property>
</bean>

在测试类中写如下代码:

1
2
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Students stu = (Students) ac.getBean ("students");

这样得到的 Students 对象与自己直接 new 的对象是一样的,都是 Students 的对象,但是这样可能看不出 Spring 框架的作用到底在哪里,怎么反倒变得复杂了?

从 HelloWorld 解释 IOC

IOC 发展史

假设有一个课程接口 Cource,分别有两个实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ICource {
void learn();
}

class JavaCource implements ICource{
@Override
public void learn() {
System.out.println (" 学习 Java");
}
}

public class HtmlCource implements ICource{
@Override
public void learn() {
System.out.println (" 学习 HTML");
}
}

接下来有一个学生类和简单工厂类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CourceFactory {
public static ICource getCource(String type) {
if("HtmlCource".equals (type)) {
return new HtmlCource();
}else{
return new JavaCource();
}
}
}

public void learn(String str) {
// 通过工厂的方式获取对象
ICource cource = CourceFactory.getCource (str);
cource.learn ();
}

这样就实现了 new 操作解耦合,但是本质上还是没变:因为对象还是由客户端创造的,而且这个简单工厂的局限性非常强,只能生产 Cource 类的对象,如何让这个工厂更强悍呢?或者说如何从根本上解决对象的管理(生产)呢?这个时候 Spring 诞生了,我们只需要在 applicationContext.xml 中配置两个具体实例就行了:
1
2
<bean id="HtmlCource" class="com.xpu.history.HtmlCource"></bean>
<bean id="JavaCource" class="com.xpu.history.JavaCource"></bean>

这样的话学生类的 learn 可以这样写:
1
2
3
4
5
public void learn(String str) {
// 通过 Spring 的 IOC 容器来获取课程对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
((ICource) context.getBean (str)).learn ();
}

很多人看到这个 HelloWorld 可能觉得并没有什么深刻的理解,接下来听我详细到来,首先我们要获得 Students 对象,最开始是我们自己 new 的 Students 对象,但是后来我们拿到的对象是是直接从配置文件里定义的对象,我们暂且把这个配置文件理解成 IOC 容器,IOC 为什么叫做 “控制反转”,IOC 不是什么技术,而是一种设计思想。在 Java 开发中,Ioc 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好 Ioc 呢?理解好 Ioc 的关键是要明确 “谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

谁控制谁,控制什么 :传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 Ioc 容器来控制对 象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了 :有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

用图例说明一下,传统程序设计和 IOC 设计,都是主动去创建相关对象然后再组合起来:
mark
IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了 “主从换位” 的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。

IoC 很好的体现了面向对象设计法则之一就是好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。这一段摘自:《谈谈对 Spring IOC 的理解》

IOC 与 DI

IOC 在被改名之前叫做控制反转,但是改名之后叫做依赖注入,改名之后
似乎更加好理解了一点,不过有一句话还是经典:控制反转是目的、依赖注入是手段,它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象,下面我们用一个示例来体现依赖注入,注入就不再解释,在上述例子中你可以理解为把对应的元素值注入到基础属性中,再把基础属性注入到对象中,于是你获得了 xml 文件中配置的对象,那么什么是依赖呢?

依赖注入底层是根据反射来设计的!

假设现在有一个 Teacher 类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Teacher {
private String teaName;
private int teaAge;
public String getTeaName() {...}
public void setTeaName(String teaName) {...}
public int getTeaAge() {...}
public void setTeaAge(int teaAge) {...}
public Teacher() {...}
public Teacher(String teaName, int teaAge) {...}
@Override
public String toString() {...}
}

又有一个课程类,很显然课程是依赖于老师的,所以设计如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Cource {
private String courseName;
private int courseHour;
private Teacher teacher;// 授课老师
public Cource() { }
public Cource(String courseName, int courseHour, Teacher teacher) {...}
public String getCourseName() {...}
public void setCourseName(String courseName) {...}
public int getCourseHour() {...}
public void setCourseHour(int courseHour) {...}
public Teacher getTeacher() {...}
public void setTeacher(Teacher teacher) {...}
@Override
public String toString() {...}
}

在配置文件中应该这样写:
1
2
3
4
5
6
7
8
9
10
<bean id="Teacher" class="com.xpu.ioc.Teacher">
<property name="teaName" value="Tim"></property>
<property name="teaAge" value="35"></property>
</bean>

<bean id="Cource" class="com.xpu.ioc.Cource">
<property name="courseName" value="Java"></property>
<property name="courseHour" value="2"></property>
<property name="teacher" ref="Teacher"></property>
</bean>

这样便完成了依赖,Cource 类依赖于 Teacher 类,所以我们在配置文件中配置 ref 属性为 Teacher,在通过 IOC 容器拿到 Cource 类的对象的时候便会自动生成 Teacher 类的对象,完成 Cource 类对象的依赖对象!Spring 的依赖注入主要有四中方式:set 注入方式、静态工厂注入方式、构造方法注入方式、基于注解的方式:
1
2
3
4
5
6
7
8
9
10
<bean id="Cource" class="com.xpu.ioc.Cource>
<!-- 使用 set 方法赋值 -->
<property name="courseName" value="Java"></property>
<property name="courseHour" value="2"></property>
<property name="teacher" ref="Teacher"></property>

<!-- 通过构造方法赋值 (name、type、index 等是可选项) -->
<constructor-arg value="JavaScript" name="courseName"></constructor-arg>
<constructor-arg value="3" type="int"></constructor-arg> <constructor-arg ref="Teacher"></constructor-arg>
</bean>

通过构造方法赋值,如果与构造方法的参数顺序不一致,则要通过 type 或者 index 或 name 指定,通过命名空间注入,引入 P 命名空间:
1
xmlns:p="http://www.springframework.org/schema/p"

1
2
<bean id="Teacher" class="com.xpu.ioc.Teacher" p:teaName="Tom" p:teaAge="23">
<bean id="Cource" class="com.xpu.ioc.Cource" p:courseName="C++" p:courseHour="5" p:teacher-ref="Teacher">

示例:注入各种集合类:
1
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
package com.xpu.collection;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class AllCollectionType {
private List<String> listElement;
private String [] arrayElement;
private Set<String> setElement;
private Map<String, String> mapElement;
private Properties propsElement;
public List<String> getListElement() {
return listElement;
}
public void setListElement(List<String> listElement) {
this.listElement = listElement;
}
public String [] getArrayElement () {
return arrayElement;
}
public void setArrayElement(String [] arrayElement) {
this.arrayElement = arrayElement;
}
public Set<String> getSetElement() {
return setElement;
}
public void setSetElement(Set<String> setElement) {
this.setElement = setElement;
}
public Map<String, String> getMapElement() {
return mapElement;
}
public void setMapElement(Map<String, String> mapElement) {
this.mapElement = mapElement;
}
public Properties getPropsElement() {
return propsElement;
}
public void setPropsElement(Properties propsElement) {
this.propsElement = propsElement;
}
@Override
public String toString() {
return "AllCollectionType [listElement=" + listElement + "\narrayElement=" + Arrays.toString (arrayElement)
+ "\nsetElement=" + setElement + "\nmapElement=" + mapElement + "\npropsElement=" + propsElement + "]";
}
}


set、list、数组、都有自己的标签,但是 list 可以混用
1
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
<!-- 复杂类型的赋值的写法 -->
<bean id="collection" class="com.xpu.collection.AllCollectionType">
<property name="listElement">
<list>
<value> 足球 < span class="tag"></value>
<value> 排球 < span class="tag"></value>
</list>
</property>
<property name="arrayElement">
<array>
<value>111</value>
<value>222</value>
</array>
</property>
<property name="setElement">
<set>
<value> 足球 < span class="tag"></value>
<value> 排球 < span class="tag"></value>
</set>
</property>
<property name="mapElement">
<map>
<entry>
<key>
<value>foot</value>
</key>
<value> 足球 < span class="tag"></value>
</entry>
<entry>
<key>
<value>basket</value>
</key>
<value> 篮球 < span class="tag"></value>
</entry>
<entry>
<key>
<value>hand</value>
</key>
<value> 排球 < span class="tag"></value>
</entry>
</map>
</property>
<property name="propsElement">
<props>
<prop key="foot"> 足球 < span class="tag"></prop>
<prop key="hand"> 排球 < span class="tag"></prop>
</props>
</property>
</bean>

value="" 方式与 <value></value> 方式的区别:

使用子元素 <value> 注入 使用 value 属性注入
参数值位置 写在首尾标签 <value></value> 之间,不加双引号 写在 value 属性中的至必须加上双引号
type 属性 有(可选),可以通过 type 指定数据类型 无,但是在构造注入的时候可以使用 type
参数值包含的特殊字符 两种处理方式:1、使用 <![CDATA { }]> 标记;2、使用 XML 预定义的实体引用 一种处理方式:使用 XML 预定义的实体引用

给属性赋空值需要如下写法:

1
2
3
4
<!-- 赋值为 null -->
<property name="name"> </null> </property>
<!-- 赋值为 & quot;" -->
<property name="name"> <value></value> </property>

在 IOC 容器定义 bean,必须定义无参构造

IOC 自动装配(只适用于引用类型,基本类型无法装配),这种方式必须满足:在当前的 IOC 容器中只能有一个 Bean 是满足要求的:

1
2
3
4
5
<!-- Course 类中有一个 ref 属性,属性名为 teacher,并且该 IOC 容器中恰好有一个 id 也是 teacher -->
<bean id="Cource" class="com.xpu.ioc.Cource" autowire="byName">
<property name="courseName" value="Java"> </property>
<property name="courseHour" value="5"></property>
</bean>

byName:byName 是自动寻找其他 JavaBean 的 id 值与该类的 ref 属性名一致的

byType:上面 autowire 的属性可以是 byType,byType 会自动寻找其他 JavaBean 的类型与该类的 ref 属性类型一致的

constructor:constructor 会看其他的 JavaBean 是否与该类的构造方法参数的类型一致

在根标签设置所有类的自动装配方式:

1
default-autowire="byName"

但是字标签的优先级更高,所以灵活度很大,接下来说说自动装配的缺点: 可读性差,容易出错,不建议经常使用

使用注解定义 Bean:通过注解的方式将 Bean 以及相应的属性值放入 IOC 容器

1
2
3
//<bean id="StudentDaoImpl" class="com.xpu.dao.StudentDaoImpl"></bean>
@Component ("StudentDaoImpl")
public class StudentDaoImpl { ... }

在配置文件中配置:

1
2
3
4
<!-- 先加入 context -->
xmlns:context="http://www.springframework.org/schema/context"
<!-- 配置扫描器,多个包使用 & quot;," 隔开 -->
<context:component-scan base-package="com.xpu.dao"></context:component-scan>

在启动的时候,扫描器会扫描在配置中的包,而且含有 @Component 注解的类,如果有则将 Bean 加入到 IOC 容器中

@Component 的细化,在不知道使用那一层的情况下就用 @Component 注解:

使用场景 注解
dao 层 @Repository
service 层 @Service
控制器层 (MVC) @Controller

结语

如果文中有错误,希望大家能够及时指出,我的邮箱是:zouchanglin@stu.xpu.cn,对了,今天是除夕,晒晒家里的年夜饭吧:
mark