基本内容

spring框架的三大特性 : IOC \ AOP \ 声明式事务

IOC ** : 首先,IOC是个容器,可以帮我们管理对象的整个声明周期。代码实现IOC之后,可以用spring来管理对象,其管理的对象叫做组件,也叫做bean**。spring管理bean有两种形式 :

  • 基于xml管理bean
  • 基于注解管理bean

这里讲的主要是基于xml管理bean,主要内容包括 :

  • xml配置bean
  • xml获取bean
  • xml注入依赖
  • 为特殊类型赋值(类类型、数据类型、集合类型等)
  • bean的作用域
  • bean的生命周期
  • FactoryBean
  • 基于xml的自动装配

配置bean

在一个maven module中,当有了一个类之后(比如helloworld),可以通过在对应目录的resources中右击Xml configuration -spring config来创建一个xml文件,并在其中添加以下内容 :

1
2
3
4
5
6
7
8
9
10
<!--
HelloWorld是一个类。
配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<bean id="helloworld" class="com.atguigu.spring.bean.HelloWorld"></bean>
<bean id="helloworld1" class="com.atguigu.spring.bean.HelloWorld"></bean>

这样就完成了在xml中配置bean。

获取bean(三种方式)

验证方法 : 创建一个测试类,先获取IOC容器,再通过IOC来获取bean

获取bean有三种方式:

  1. 根据id获取
  2. 根据类型获取,此时要求ioc容器中有且只有一个ioc类型匹配的bean(常用)
  3. 根据类型和id

相应的实现 : 在对应目录的test目录下创建helloworldTest类,加入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testHelloWorld(){
//获取IOC容器 (通过类路径获取)
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器中的bean
//1. 可以根据name(xml中的id)获取。由于根据name获取的不知道类型,因此做一个 (HelloWorld)强转
HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
//2. 根据类型获取。这里会报错,因为有两个Hellworld类型的bean
HelloWorld helloworld = ac.getBean(HelloWorld.class);
//3. 根据id和类型获取
HelloWorld helloworld = ac.getBean("helloworld", HelloWorld.class);
helloworld.sayHello();
}

运行此测试文件,就可以获取bean并且输出hellospring。

扩展 :

  1. 如果组件类实现接口 ,根据接口类型可以获取 bean 吗?

答 :可以,前提是bean唯一

  1. 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

    答:不行,因为bean不唯一

依赖注入(两种方式)

假如有这样一个Student类,我们来为他进行依赖注入。(依赖指的就是id、name这些属性,因为Student类依赖于这些属性)

DI ,是IOC的一种实现方式,就是为类中的属性进行赋值。依赖注入有两种方式 : setter注入与构造器注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private Integer id;
private String name;
public Student() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
  • setter注入 :配置bean时为属性赋值,property标签

    1
    2
    3
    4
    5
    6
    7
    <bean id="studentOne" class="com.atguigu.spring.bean.Student">
    <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
    <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)-->
    <!-- value属性:指定属性值 -->
    <property name="id" value="1001"></property>
    <property name="name" value="张三"></property>
    </bean>
  • 构造器注入(需要添加有参构造),constrcutor标签

    先添加有参构造

    1
    2
    3
    4
    5
    6
    7
    8
    public Student(Integer id, String name) {
    this.id = id;
    this.name = name;
    }
    public Student(Integer sorce, String name) {
    this.sorce = sorce;
    this.name = name;
    }

    再注入

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="studentTwo" class="com.atguigu.spring.bean.Student">
    <!-- 这里是按顺序添加,当然也可以通过index属性和name属性进一步描述构造器参数 -->
    <!-- 如果有多个构造器,那么会首先匹配参数个数相同的构造器 -->
    <!-- 如果有多个构造器参数个数也相同,那么就可以使用name属性了,例如下面就把1002赋给sorce而不是id
    <constructor-arg value="1002" name="sorce"></constructor-arg> -->
    <constructor-arg value="1002"></constructor-arg>
    <constructor-arg value="李四"></constructor-arg>
    </bean>

上面说的value是简单的字符串形式,如果某个实例本身是某个特殊的属性该怎么赋值 ?

为特殊类型赋值

为类类型属性赋值

试想这样一个类 :教室类,其中包含教室号与教室名称

1
2
3
4
5
6
public class Clazz {
private Integer clazzId;
private String clazzName;
//省略getter与setter ...
//省略构造器 ...
}

然后要把这个教室类加入到学生类中 :在学生类中添加如下实例域与构造方法 :

1
2
3
4
5
6
7
private Clazz clazz;
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}

此时如果要给student类中的class类赋值 ,可以有三种方法 :

  • 引用外部已经声明的bean

    首先要在外部声明class类的bean

    1
    2
    3
    4
    <bean id="clazzOne" class="com.atguigu.spring.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="1班"></property>
    </bean>

    然后在student的bean中引用 : 注意是ref关键字,不是vaule

    1
    2
    3
    4
    <bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    </bean>
  • 内部bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <property name="clazz">
    <!-- 在一个bean中再声明一个bean就是内部bean -->
    <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
    <bean id="clazzInner" class="com.atguigu.spring.bean.Clazz">
    <property name="clazzId" value="2222"></property>
    <property name="clazzName" value="2班"></property>
    </bean>
    </property>
    </bean>
  • 级联属性赋值

    1
    2
    3
    4
    5
    6
    <bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
    </bean>

为数组类型属性赋值

如果student类中有数组类型的实例域 ,比如有若干个爱好:

1
2
3
4
5
6
7
private String[] hobbies;
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}

那么配置bean的方法如下 :

1
2
3
4
5
6
7
8
9
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
<property name="hobbies">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<//bean>

为集合类型属性赋值

比如在class类中 ,有一个students的集合 :

1
2
3
4
5
6
7
private List<Student> students;
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}

那么赋值方式如下 :

1
2
3
4
5
6
7
8
9
<bean id="clazzTwo" class="com.atguigu.spring.bean.Clazz">
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
</bean>

P命名空间

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

1
2
3
<bean id="studentSix" class="com.atguigu.spring.bean.Student"
p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap">
</bean>

bean的作用域

spring IOC所管理的bean,默认是单例模式,可通过如下代码实现 :

1
2
3
4
5
6
7
8
@Test
public void testHelloWorld(){
//获取IOC容器 (通过类路径获取)
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User user1 = ac.getBean(User.class);
User user2 = ac.getBean(User.class);
System.out.println(user1==user2); //true
}

如果不想实现单例,在实现DI的时候,把scope标签写成prototype(原型 :多例)即可 (默认为singleton,单例)

1
<bean class="com.atguigu.bean.User" scope="prototype"></bean>

如果是在WebApplicationContext环境下还会有另外两个作用域 (不常用)

  • request : 在一个请求范围内有效

  • session : 在一个会话范围内有效

bean的生命周期

什么时候被创建?什么时候被初始化? …… ? 什么时候销毁?

具体的生命周期如下 :

  1. 实例化
  2. 依赖注入
  3. 初始化,需要通过bean的init-method属性指定初始化方法
  4. IOC容器关闭时销毁,需要通过bean的destory-method属性指定销毁的方法

注意:不同的bean作用域(单例、多例),对生命周期有影响,详见下面的test方法

以下面这个User类来举例 :

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
public class User {
private Integer id;
private String username;
private String password;
private Integer age;

public User() {
System.out.println("生命周期:1、创建对象(实例化)");
}

public User(Integer id, String username, String password, Integer age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}

public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("生命周期:2、依赖注入");
this.id = id;
}
// 省略一些setter和getter ...
public void initMethod(){
System.out.println("生命周期:3、初始化");
}
public void destroyMethod(){
System.out.println("生命周期:5、销毁");
}
}

配置它的bean:

1
2
3
4
5
6
7
8
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1001"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="23"></property>
</bean>

相应的测试类

1
2
3
4
5
6
7
8
9
@Test
public void testLife(){
//获取ioc容器,如果是单例模式这一步直接执行前三步 :实例化、依赖注入、初始化 (因为单例模式下每一次获取的bean都是同一个对象,所以在这里创建就ok)
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
//获取对象,如果是多例模式,这一步才执行前三步 :实例化、依赖注入、初始化
User bean = ac.getBean(User.class);
System.out.println("生命周期:4、通过IOC容器获取bean并使用");
ac.close(); //这里会输出 "生命周期:5、销毁"
}

FactoryBean

FactoryBean是一个接口,Spring提供的一种整合第三方框架的常用机制。

和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是它的实现类getObject()方法的返回值。

FactoryBean的源码如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//接口 ,泛型就是工厂所提供的类型 ,需要创建一个类实现该接口
public interface FactoryBean<T> {

String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
//获取对象
@Nullable
T getObject() throws Exception;
//获取对象的类型
@Nullable
Class<?> getObjectType();
//是否单例
default boolean isSingleton() {
return true;
}
}

实现方法 :

  1. 创建类UserFactoryBean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class UserFactoryBean implements FactoryBean<User> {
    //提供的对象
    @Override
    public User getObject() throws Exception {
    return new User();
    }
    //提供的对象类型
    @Override
    public Class<?> getObjectType() {
    return User.class;
    }
    }
  2. 创建配置文件配置bean

    1
    2
    3
    **在加载com.atguigu.bean.UserFactoryBean时,实际上是把UserFactoryBean中的getObject()方法所返回的对象交给了IOC**
    **因此这里没有配置User类型的bean,在下面的测试文件中却可以获取User类型的bean**
    <bean id="user" class="com.atguigu.bean.UserFactoryBean"></bean>
  3. 测试文件

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void testUserFactoryBean(){
    //获取IOC容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml");
    //因此这里可以直接获取User类型的bean,这就是UserFactoryBean的好处
    User user = (User) ac.getBean("user");
    System.out.println(user);
    }

基于xml的自动装配

自动装配:根据指定的策略(byType,byName),在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型接口类型属性赋值

场景模拟

通过经典的三层架构来进行场景模拟 : 控制层、业务层、持久层。控制层调用业务层来处理业务逻辑,业务层调用DAO实现持久化操做。

控制层 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserController {
//如果是private UserService userService = new UserServiceImpl();则是硬编码
//因此IOC容器的意义就体现出来 :用来管理对象和对象之间的依赖关系,因此将UserController、UserServiceImpl、UserDaoImpl 都交给容器管理

//成员变量,可以在配置文件中通过setter为其赋值 : 赋值的是什么,用的就是什么
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}

//模拟场景 : 调用 UserService 中的saveUser()来处理业务逻辑
public void saveUser(){
userService.saveUser();
}
}

业务层(接口) :

1
2
3
public interface UserService {
void saveUser();
}

创建类UserServiceImpl实现接口UserService

1
2
3
4
5
6
7
8
9
10
11
12
public class UserServiceImpl implements UserService {
//交给IOC管理
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void saveUser() {
//处理业务逻辑时需要调用DAO中的方法
userDao.saveUser();
}
}

持久层 :

1
2
3
public interface UserDao {
void saveUser();
}

创建类UserDaoImpl实现接口UserDao

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}

在resource下面创建spring的xml配置文件

如果不用自动装配,交给IOC管理:

1
2
3
4
5
6
7
<bean id="userController" class="com.atguigu.autowire.xml.controller.UserController">
<property name="userService" ref="userService"></property>
</bean>
<bean id="userService" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" >
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>

创建测试类 :

1
2
3
4
5
6
@Test
public void testAutoWireByXML(){
ApplicationContext ac = new ClassPathXmlApplicationContext("autowire-xml.xml");
UserController userController = ac.getBean(UserController.class);
userController.saveUser();
}

自动装配的两种方式

通过autowire 来设置自动装配策略。autowire的属性有 : no & default :都是不匹配 ,byType ,byName

  • byType (常用):根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

    1
    2
    3
    4
    5
    <bean id="userController" class="com.atguigu.autowire.xml.controller.UserController" autowire="byType">
    </bean>
    <bean id="userService" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType">
    </bean>
    <bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
  • byName(少用):将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值