Spring学习(三):IOC容器详解

什么是IOC

Inversion of Control的缩写,中文译为控制反转,简单来说就是把对象创建和对象之间的调用过程,交给 Spring 进行管理。
创建对象实例的控制权从代码控制剥离到IOC容器控制,实际就是你在xml文件控制,侧重于原理

底层原理

  • XML解析
    使用dom4j解析
  • 工厂模式
  • 反射创建类对象
1
2
3
Class clazz=Class.forName(classValue);
UserService service=clazz.newInstance();
return service;

XML注入实现IOC

注入常规类型属性

set方法注入

  • 第一步 在类中定义属性和对应的 set 方法
  • 第二步 在XML文件中添加property标签
1
2
3
4
5
<!--配置User对象-->
<bean id="user" class="com.spring5.User">
<!--name的值对应于javaBean中的属性值-->
<property name="name" value="小明"></property>
</bean>

有参构造注入

  • 第一步 在类中定义有参构造方法
  • 第二步 在XML文件中添加constructor-arg标签
1
2
3
4
5
<!--配置User对象-->
<bean id="user" class="com.spring5.User">
<!--name的值对应于javaBean中的属性值-->
<constructor-arg name="name" value="张三"></constructor-arg>
</bean>

P空间注入(了解)

  • 第一步 在类中定义属性和对应的 set 方法
  • 第二步 修改XML文件
    在beans中添加xmlns:p属性;在bean中添加p:name属性
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">
<!--配置User对象-->
<bean id="user" class="com.spring5.User" p:name="李四"></bean>
</beans>

注入特殊类型属性

空值

1
2
3
<property name="name">
<null></null>
</property>

特殊符号

  • 方法一 把<>进行转义

    1
    2
    <property name="name" value="&lt;&lt;王五&gt;&gt;">
    </property>
  • 把带特殊符号内容写到 CDATA

    1
    2
    3
    <property name="name">
    <value><![CDATA[<<王五>>]]></value>
    </property>

外部Bean

现有类UserService、接口UserDao、类UserDaoImpl,其中UserDaoImpl实现了接口UserDao。代码结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
src
│ bean.xml

└─com
└─spring5
│ Test.java

├─dao
│ UserDao.java
│ UserDaoImpl.java

└─service
UserService.java

如果类UserService要调用类UserDaoImpl的方法,传统做法是创建UserDao对象然后调用其方法,这种做法耦合太高。用spring注入的步骤如下:

  • 在UserService中创建属性UserDao,并写好set方法
  • 在XML中配置UserService和UserDao对象
  • 通过property标签注入UserDao对象

示例代码如下:
接口UserDao:

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

类UserDaoImpl:

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("I am dao update...");
}
}

类UserService:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserService {
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

@Test
public void add(){
System.out.println("I am service add...");
userDao.update();
}
}

bean.xml:

1
2
3
4
5
6
7
<bean id="userDaoImpl" class="com.spring5.dao.UserDaoImpl"></bean>
<!-- name属性:类里面的属性名称
ref属性:bean标签id值-->
<bean id="userService" class="com.spring5.service.UserService">
<property name="userDao" ref="userDaoImpl">
</property>
</bean>

内部Bean和级联配置

简单来说就是bean标签里面嵌套bean标签。在员工和部门,一个部门有多个员工,一个员工属于一个部门。示例代码如下:
Dept类:

1
2
3
4
5
6
public class Dept {
private String dname;
public void setDname(String dname) {
this. dname = dname;
}
}

Emp类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this. dept = dept;
}
public void setEname(String ename) {
this. ename = ename;
}
public void setGender(String gender) {
this. gender = gender;
}
}
内部Bean配置
1
2
3
4
5
6
7
8
9
10
11
12
<!--内部 bean-->
<bean id= "emp" class= "com.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name= "ename" value= "lucy"></property>
<property name= "gender" value="女"></property>
<!--设置对象类型属性-->
<property name= "dept">
<bean id= "dept" class= "com.spring5.bean.Dept">
<property name= "dname" value="技术部"></property>
</bean>
</property>
</bean>
级联配置

Emp类需要生成getDep的方法

1
2
3
public Dept getDept(){
return dept;
}

XML文件中dept.dname获取属性

1
2
3
4
5
6
7
8
<!--级联赋值-->
<bean id= "emp" class= "com.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name= "ename" value= "lucy"></property>
<property name= "gender" value="女"></property>
<!--级联赋值-->
<property name= "dept.dname" value="商务部"></property>
</bean>

注入集合属性

普通注入

新建类User,包含集合属性和相关set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class User {

private String[] arrs;// 数组
private List<String> list;// list
private Map<String,String> maps;// map
private Set<String> sets;// set

public void setArrs(String[] arrs) {
this.arrs = arrs;
}

public void setList(List<String> list) {
this.list = list;
}

public void setMaps(Map<String, String> maps) {
this.maps = maps;
}

public void setSets(Set<String> sets) {
this.sets = sets;
}
}

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
<bean  id= "user"  class= "com.spring5.User">
<!--数组类型属性注入-->
<property name= "arrs">
<array>
<value>JAVA课程</value>
<value>mySQL课程</value>
</array>
</property>
<!--list 类型属性注入-->
<property name= "list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--map 类型属性注入-->
<property name= "maps">
<map>
<entry key= "JAVA" value= "java"></entry>
<entry key= "PHP" value= "php"></entry>
</map>
</property>
<!--set 类型属性注入-->
<property name= "sets">
<set>
<value>MySQL</value>
<value>MySQL</value>
<value>Oracle</value>
<value>Oracle</value>
</set>
</property>
</bean>

注入对象集合

Dept类

1
2
3
4
5
6
7
8
public class Dept {
//部门有多个员工
private List<Emp> empList;

public void setEmpList(List<Emp> empList) {
this.empList = empList;
}
}

Emp类

1
2
3
4
5
6
7
public class Emp {
private String eName;

public void seteName(String eName) {
this.eName = eName;
}
}

XML配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<bean  id= "dept"  class= "com.spring5.Dept">
<!--注入 list集合类型,值是对象-->
<property name="empList">
<list>
<ref bean="emp1"></ref>
<ref bean="emp2"></ref>
<ref bean="emp3"></ref>
</list>
</property>
</bean>
<bean id="emp1" class="com.spring5.Emp">
<property name="eName" value="张三"></property>
</bean>
<bean id="emp2" class="com.spring5.Emp">
<property name="eName" value="李四"></property>
</bean>
<bean id="emp3" class="com.spring5.Emp">
<property name="eName" value="王五"></property>
</bean>

自动装配

Spring可以根据属性名称或者类型进行自动装配,使用byType时相同类型的Bean不能配置多个

1
2
3
4
<bean  id= "emp"  class= "com.spring5.autowire.Emp"  autowire="byType">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id= "dept" class= "com.spring5.autowire.Dept"></bean>

外部属性文件

一、创建外部属性文件jdbc.properties,填写数据库信息,key随便写

1
2
3
4
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root

二、把外部 properties 属性文件引入到 spring 配置文件中

  • 引入 context 名称空间
    xmlns:context=“http://www.springframework.org/schema/context”
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring- - context.xsd"

  • 在 spring 配置文件使用标签引入外部属性文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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 http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
</beans>

工厂Bean

Spring 有两种类型:普通 bean和工厂bean(FactoryBean)。普通 bean在配置文件中定义的bean类型和返回类型一样,而工厂bean在配置文件定义 bean类型可以和返回类型不一样。实现工厂Bean的步骤如下:

  • 第一步 创建类,让这个类作为工厂 bean,实现FactoryBean接口
  • 第二步 重写接口里面的方法,在方法中定义返回的bean类型

示例代码如下:
MyBean和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
33
34
public class MyBean implements FactoryBean<User> {

@Override
public User getObject() throws Exception {
User user = new User();
user.setName("张三");
return user;
}

@Override
public Class<User> getObjectType() {
return null;
}

@Override
public boolean isSingleton() {
return false;
}
}

public class User {

private String name;

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

public void test(){
System.out.println("I am test...");
}


}

bean.xml

1
<bean id="myBean" class="com.spring5.MyBean"></bean>

Test类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {

@org.junit.Test
public void test() {
//1 加载 spring 配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
//2 获取配置创建的对象
User user = context.getBean("myBean", User.class);
System.out.println(user);
user.test();
}
}

运行结果:
com.spring5.User@42d8062c
I am test…

Bean的作用域

作用域 说明
单例(singleton) 默认)每一个Spring IoC容器都拥有唯一的一个实例对象
原型(prototype) 一个Bean定义,任意多个对象
请求(request) 一个HTTP请求会产生一个Bean对象,也就是说,每一个HTTP请求都有自己的Bean实例。只在基于web的Spring ApplicationContext中可用
会话(session) 限定一个Bean的作用域为HTTPsession的生命周期。同样,只有基于web的Spring ApplicationContext才能使用
全局会话(global session) 限定一个Bean的作用域为全局HTTPSession的生命周期。通常用于门户网站场景,同样,只有基于web的Spring ApplicationContext可用

Bean的生命周期

  • 第一步,执行无参构造创建Bean实例

  • 第二步,调用类的set方法设置属性值

  • 第三步,调用预初始化方法postProcessBeforeInitialzation()
    需要有Bean实现了BeanPostProcessor接口

  • 第四步,调用bean的初始化的方法
    需要在XML的bean属性中进行配置

    1
    init-method="初始化调用方法名"
  • 第五步,调用后初始化方法postProcessAfterInitialization()
    需要有Bean实现了BeanPostProcessor接口

  • 第六步,对象获取到了,bean可以使用了

  • 第七步,当容器关闭时调用bean的销毁的方法
    需要在XML的bean属性中进行配置

    1
    destroy-method="销毁调用方法名"

实例代码如下:
User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class User {

private String name;

public User() {
System.out.println("第一步 执行无参构造创建Bean实例");
}

public void setName(String name) {
this.name = name;
System.out.println("第二步 调用set方法设置属性值");
}
public void initMethod(){
System.out.println("第四步 调用bean的初始化的方法");
}
public void destroyMethod(){
System.out.println("第七步 当容器关闭时调用bean的销毁的方法");
}
public void test(){
System.out.println("I am test...");
}
}

MyBeanPost类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步,调用预初始化方法postProcessBeforeInitialzation()");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步,调用后初始化方法postProcessAfterInitialization()");
return bean;
}
}

Test类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {

@org.junit.Test
public void test() {
//1 加载 spring 配置文件
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean.xml");
//2 获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
System.out.println("第六步 对象获取到了,bean可以使用了");
user.test();
context.close();
}
}

bean.xml

1
2
3
4
5
6
<!--配置User对象-->
<bean id="user" class="com.spring5.User" init-method="initMethod" destroy-method="destroyMethod">
<!--name的值对应于javaBean中的属性值-->
<property name="name" value="小明"></property>
</bean>
<bean id="myBeanPost" class="com.spring5.MyBeanPost"></bean>

注解方式实现IOC

注解概述

  • 注解是代码特殊标记
    语法:@注解名称(属性名称=属性值, 属性名称=属性值…)
  • 怎么用注解
    注解可以用在类、方法和属性上面
  • 使用注解目的
    简化 xml 配置

Spring针对 Bean管理中创建对象提供的注解

  • @Component
  • @Service
  • @Controller
  • @Repository
    上面四个注解功能是一样的,都可以用来创建 bean 实例,在服务层用@Service、在控制层用@Controller只是方便管理

实现对象创建

  • 第一步 引入依赖
    把spring-aop-5.2.6.RELEASE.jar包引入到IDEA中
  • ​ 第二步 开启组件扫描
    需要引入context名称空间:xmlns:context=“http://www.springframework.org/schema/context”、http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
1
2
3
4
5
6
<!--开启组件扫描
如果扫描多个包可以用下面两种方式实现:
1 多个包使用逗号隔开
2 扫描包上层目录
-->
<context:component-scan base-package="com.spring5"></context:component-scan>
  • 第三步 创建类,在类上面添加注解
    注解中的value值可以省略,默认值是类名首字母小写
1
2
3
4
5
6
@Component(value = "userService")
public class UserService {
public void add(){
System.out.println("I am UserService add...");
}
}

扫描细节配置

  • 不加use-default-filters="false"表示会扫描包下面的所有注解
  • context:include-filter 配置扫描哪些内容
  • context:exclude-filter配置不扫描哪些内容

示例一

1
2
3
4
5
<!--扫描包com.spring5下面@Controller注解-->
<context:component-scan base-package="com.spring5" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

示例二

1
2
3
4
5
<!--扫描包com.spring5下面除了@Controller以外的所有注解-->
<context:component-scan base-package="com.spring5">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>