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">
<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)); } }
|