​ Spring是轻量级的JavaEE开源框架,可以降低企业应用开发的复杂性。

​ Spring的两个核心部分是IOC(控制反转,Inversion of Control)和AOP(面向切面编程, Aspect Oriented Programming)。我会分别用两篇博客来介绍。这篇博客将主要介绍IOC的内容。

​ Spring有以下的一些特点:

  • 方便解耦,简化开发
  • AOP支持
  • 方便测试
  • 方便与其他框架整合
  • 对事务的管理
  • 降低API开发难度

IOC简介

​ 我们在学习OOP和设计模式的过程中,很重要的一个点就是降低耦合度,提高程序的可扩展性。Spring给了这个问题一个全新的答案,IOC或者说DI(依赖注入,Dependencies Injection)。IOC和DI可以简单理解成一个意思,或者更加具体的说,DI是对IOC思想的一种实现方式。

​ 那么什么叫控制反转呢?在普通的Java程序中,如果我们想使用一个类,那就首先需要使用new或者其他方式创建一个对象,而在程序中创建对象实际上就建立了两个类之间的耦合关系。在Spring中,我们把对象交给Spring进行管理,也称为bean。当需要使用时,直接向spring的IOC容器要一个实例即可。这种方式显著降低了类与类之间的耦合度。

通过xml文件进行依赖注入

​ 在Spring中,主要有xml配置文件和注解两种方式进行依赖注入。现在使用注解更多,但了解xml的配置方式更容易理解Spring管理bean的整个过程。

基础知识

​ 这一节主要是xml注入的基础知识,我会通过以下的实例代码进行说明。

代码

application.xml

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
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student1" class="xmlDI.basic.Student">
<property name="id" value="1"/>
<property name="name" value="tom"/>
</bean>

<bean id="student2" class="xmlDI.basic.Student" p:id="2" p:name="alice"/>

<bean id="student3" class="xmlDI.basic.Student">
<property name="id" value="3"/>
<property name="name" value="bob"/>
<property name="teacher" ref="teacher1"/>
</bean>

<bean id="student4" class="xmlDI.basic.Student">
<property name="id" value="4"/>
<property name="name" value="max"/>
<property name="teacher">
<bean class="xmlDI.basic.Teacher">
<property name="name" value="teacher2"/>
<property name="id" value="1111"/>
</bean>
</property>
</bean>

<bean id="teacher1" class="xmlDI.basic.Teacher" p:id="123" p:name="teacher1"/>

<bean id="teacher3" class="xmlDI.basic.Teacher" p:id="12345" p:name="teacher2" >
<property name="students">
<list>
<value>a</value>
<value>b</value>
<value>c</value>
</list>
</property>
</bean>

<bean id="factory" class="xmlDI.basic.Factory"/>

</beans>
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
package xmlDI.basic;

public class Student {
private Long id;
private String name;
private Teacher teacher;

public Long getId() {
return id;
}

public String getName() {
return name;
}

public Teacher getTeacher() {
return teacher;
}

public void setId(Long id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", teacher=" + 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
package xmlDI.basic;

import java.util.List;

public class Teacher {
private Long id;
private String name;
private List<String> students;

public Long getId() {
return id;
}

public String getName() {
return name;
}

public List<String> getStudents() {
return students;
}

public void setId(Long id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setStudents(List<String> students) {
this.students = students;
}

@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", students=" + 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 xmlDI.basic;

import org.springframework.beans.factory.FactoryBean;

public class Factory implements FactoryBean<Student> {
@Override
public Student getObject() throws Exception {
Student student = new Student();
student.setId(66L);
student.setName("fac");
return student;
}

@Override
public Class<?> getObjectType() {
return Student.class;
}

@Override
public boolean isSingleton() {
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package xmlDI.basic;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("xmlDI/basic/application.xml");
System.out.println(context.getBean("student1", Student.class));
System.out.println(context.getBean("student2", Student.class));
System.out.println(context.getBean("student3", Student.class));
System.out.println(context.getBean("student4", Student.class));
System.out.println(context.getBean("teacher3", Teacher.class));
System.out.println(context.getBean("factory"));
}
}ja

如果运行main方法后输入下列语句,则说明Spring的环境是正常的。

例子1

xml配置文件建议使用IDE生成,如在idea中新建文件时指定spring的配置文件即可,这里就不解释xml的字段。

在Spring中,被IOC容器管理的类称为bean,因此我们主要需要关注配置文件中的多个标签,这也是我举的几个例子。

第一个bean是最基本的用法,class属性指定需要由Spring管理的类,注意需要包含完整的包名,id属性用于指定这个bean的名字,在该配置文件中不能重复。而子标签则用于注入属性的值,这里我们注入了id和name两个属性。

接下来我们需要让Spring加载该xml配置文件。在main方法中,我们通过ClassPathXmlApplicationContext类来加载xml文件,再通过getBean方法来获取由Spring管理的bean,这样,我们就输出了id为1的学生。

值得注意的是,getBean方法有两个参数,第一个是bean的id,第二个是类。他们分别都可以省略,但前提是只有一个bean可以匹配。如你可以只使用类进行getBean,但必须满足Spring只管理了对应的一个类,否则Spring无法判断你需要的是哪个bean,从而抛出异常。

例子2(p命名空间)

p命名空间是为简化属性的注入而产生的。在使用它之前,首先需要引入相应的xml命名空间,xmlns:p=”http://www.springframework.org/schema/p"。建议通过IDE生成,如果您使用的是idea,那么会在您输入需要用到该命名空间的情况下提示导入,选择导入命名空间即可。

在第二个例子中,我们使用 p:id=”2” p:name=”alice” 来代替标签。它们的作用是相同的,但p命名空间的写法更加简单。

例子3(注入非字面量)

在例子3中,我们希望为Student的teacher字段赋值,但Teacher是另一个类,无法简单的通过字面量进行赋值。这里我们使用了ref属性,用在下面定义的一个Teacher类的bean来进行注入。ref的值为对应bean的id,因为id是唯一的。

例子4(使用内部bean进行注入)

如果某一个bean只用于给另一个bean赋值,那么把它们分卡写就降低了可读性。我们可以直接把需要注入的bean声明在下,这样也能达到例子3的效果。

例子5(注入集合类)

在Java中,我们难免使用各种集合类。给集合类的字段赋值也非常简单,Spring为我们提供了许多标签。例子5是一个List的例子,其他类似。

例子6(工厂bean)

有时候,我们并不希望像上面那样直接在xml文件中注入,而是希望使用工厂模式。Spring为我们提供了FactoryBean接口。

这是一个泛型接口,需要值得的类型是返回的bean的类型。在我所给的Factory这个类中,我们可以看到FactoryBean有3个需要重写的接口,第一个getObject就是典型的工厂类的方法,返回一个实例;第二个getObjectType返回实例的类型;第三个isSingleton返回该bean是否为单例。

我们通过标签引入该工厂,然后通过getBean方法获取该工厂,会发现获取到的实际上是工厂返回的实例。

例子7(bean的作用域)

在上一个例子中,我们提到了单例模式。事实上,Spring可以指定bean是单例模式还是非单例模式,默认是单例模式,即在加载配置文件时就会生成一个唯一的实例。如果希望bean是非单例的,可以在标签中指定scope属性。scope属性在非web项目中有两个可取的值,singleton表示默认的单例模式,而prototype表示非单例模式。

bean的生命周期

代码

lifeCycle.xml

1
2
3
4
5
6
7
8
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="lifeCycle" class="xmlDI.lifeCycle.LifeCycle" p:id="1" init-method="init" destroy-method="destroy"/>

</beans>
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
package xmlDI.lifeCycle;

public class LifeCycle {
private Long id;

public LifeCycle() {
super();
System.out.println("1.获取实例");
}

public Long getId() {
return id;
}

public void setId(Long id) {
System.out.println("2.参数注入");
this.id = id;
}

public void init() {
System.out.println("3.初始化");
}

public void destroy() {
System.out.println("5.销毁");
}

@Override
public String toString() {
System.out.println("4.可以使用");
return "LifeCycle{" +
"id=" + id +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
package xmlDI.lifeCycle;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("xmlDI/lifeCycle/lifeCycle.xml");
System.out.println(context.getBean("lifeCycle"));
context.close();
}
}

解释

bean常用的生命周期过程有5个,分别是:

  • 通过构造器获取bean实例(无参数构造)
  • 调用set方法为属性赋值
  • 调用bean的初始化方法
  • bean可以使用
  • 当容器关闭时,调用bean的销毁方法

其中,初始化和销毁的方法需要在bean标签中用init-method和destroy-method两个属性指定,代码运行的结果如下:

展示了bean生命周期的五个阶段,读者可以通过代码输出对应语句的位置进行理解。

值得注意的是,ApplicationContext类是没有close方法的,需要使用其子类,这里我直接使用了ClassPathXmlApplicationContext。

引入外部资源文件

如果您有开发Java web项目的经验,那么一定有过写配置文件的经历。不管的.properties文件还是.yml文件,我们需要在配置文件中指定数据库连接的信息等一系列参数,而这些配置文件是如果引入Spring的呢?这里我用一个简单的例子进行介绍。

代码

test.properties

1
2
test.id=123
test.password=abcdef

application.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">-->
<!-- <property name="location" value="xmlDI/resource/test.properties"/>-->
<!-- </bean>-->
<context:property-placeholder location="xmlDI/resource/test.properties"/>

<bean id="resource" class="xmlDI.resource.Resource">
<property name="id" value="${test.id}"/>
<property name="password" value="${test.password}"/>
</bean>
</beans>
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
package xmlDI.resource;

public class Resource {
private Long id;
private String password;

public Long getId() {
return id;
}

public String getPassword() {
return password;
}

public void setId(Long id) {
this.id = id;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "Resource{" +
"id=" + id +
", password='" + password + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
package xmlDI.resource;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("xmlDI\\resource\\application.xml");
System.out.println(context.getBean("resource"));
}
}

解释

在配置文件中,我定义了id和password两个属性,我希望在注入的适合使用这两个值。

首先,我们需要把该配置文件引入Spring,最简单的方式是使用<context:property-placeholder location=”xmlDI/resource/test.properties”/>。其中location属性指定了配置文件的内容。这个标签使用了context命名空间,需要在xml中引入。

在该标签的上方,我给出了原始的方法,即通过PropertySourcesPlaceholderConfigurer这个类来引入配置文件,建议使用更简单的context命名空间。

在引入文件之后,我们就可以通过${}的格式使用配置文件中定义的变量,大括号中对应配置文件中等号左侧的值,即键值对的key。

自动装配

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="teacher" class="xmlDI.autowire.Teacher" p:id="1" p:name="teacher"/>

<bean id="student" class="xmlDI.autowire.Student" autowire="byType">
<property name="id" value="123"/>
<property name="name" value="student"/>
</bean>

</beans>

解释

这里我们还是以第一部分基础知识中的老师与学生为例。在配置文件中,我们在bean标签中添加autowire属性。它的作用是代替人工赋值的方式(如通过p命名空间或property标签),而是由Spring的IOC容器为我们自动赋值。

autowire有两种方式,一种是byType,一种是byName。顾名思义,byType是通过类型进行自动装配,它的前提是Spring的IOC容器中只有一个bean满足条件。byName是通过名称进行自动装配,Spring会通过set方法指定的名称(即set方法名set之后的内容)寻找符合条件的bean进行装配。

但通过xml进行自动装配有一个局限性,就是只能在bean标签中指定,面向的是一个bean中的所有属性。和后面要介绍的通过注解的方式相比,有很大局限性,因此并不推荐大家使用。

通过注解的方式进行依赖注入

注解是现在最常用的依赖注入的方式,通过前面xml文件方式的学习,我们已经了解到了Spring的IOC容器工作的方式,因此通过注解的方式非常容易学习。

常用注解介绍

@Component

@Component作用与类上,它的作用和xml文件中的bean标签非常类似,就是把该类交给Spring的IOC容器进行管理。我们可以指定value属性,对应bean的id。

@Service,@Controller,@Repository

这三个标签的作用实际上和@Component非常类似,区别在于这三个标签有更明确的语言。这三个标签主要用于web项目,@Service代表服务层,@Controller代表控制器层,@Repository代表dao层。做个相关项目的读者应该很容易理解。

@Autowired

开启自动装配,一般直接作用与类中的一个属性上,默认使用byType的自动装配方式。也可以作用与setter方法上,作用类似。如果byType自动装配无法唯一匹配,可以同时再使用@Qualifier标签指定名称。

@Resource

由jdk提供的标签,作用和@Autowired非常类似,区别是默认通过byName进行自动装配。也可以指定name和type属性。

@Value

注入普通类型属性。常用于引入配置文件中的值,格式同之前所属,即${}。

@Configuration

指定该类为配置类,可以用于代替xml配置文件

@ComponentScan

指定组件扫描的范围,Spring将在该范围内进行扫描,把通过@Component或类似注解标注的类纳入SpringIOC容器管理。

代码

1
2
3
4
5
6
7
8
9
package annotationDI;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "annotationDI")
public class SpringConfig {
}
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
package annotationDI;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Student {
private String name = "Tom";

@Autowired
private Teacher teacher;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", teacher=" + teacher +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package annotationDI;

import org.springframework.stereotype.Component;

@Component
public class Teacher {
String name = "Bob";

public String getName() {
return name;
}

@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package annotationDI;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
@Test
public void run() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(context.getBean(Student.class));
}
}