Spring——IOC
本文章使用的spring版本为5.2.18
什么是IOC
- 控制反转(IOC)就是把对象创建和对象之间的调用过程,交给 Spring 进行管理
- 使用 IOC 目的:为了耦合度降低
- IOC底层使用了xml 解析、工厂模式、反射
BeanFactory
- IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
- Spring 提供 IOC 容器实现两种方式:(两个接口)
-
BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供给开发人员进行使用- 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
-
ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用- 加载配置文件时候就会把在配置文件对象进行创建
- ApplicationContext 接口的实现类
ClassPathXmlApplicationContext
通过读取类路径下的spring配置文件来创建容器
// 类路径下的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
FileSystemXmlApplicationContext
通过读取在磁盘中的spring配置文件来创建容器
// 磁盘路径的配置文件
ApplicationContext context = new FileSystemXmlApplicationContext("E:\\IdeaProjects\\Spring\\01_Hello\\src\\application.xml");
IOC 操作 Bean 管理
-
Bean 管理指的是两个操作
- Spring 创建对象
- Spirng 注入属性
-
Bean 管理的两种方式
- 基于 xml 配置文件方式实现
- 基于注解方式实现
XML配置文件实现Bean管理
使用到的实体类
public class User {
private Integer id;
private String name;
public void sayHello(){
System.out.println("hello!");
}
}
创建对象
在配置文件中使用bean标签来注册对象
<bean id="user" class="com.wcy.pojo.User"></bean>
id:是该对象的唯一标识,通过id属性来获取对象class:是需要创建的对象的全类名
获取对象的方式是通过容器的getBean方法传入id来获取我们子配置文件中注册的bean
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
User user = context.getBean("user", User.class);
user.sayHello();
}
spring是通过无参构造方法来创建对象的,可以通过在无参构造中打印信息证明
如果覆盖掉无参构造方法时就会报错org.springframework.beans.factory.BeanCreationException
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
所以当我们自己写了构造方法时,一定需要写一个无参构造
属性注入
属性的注入又叫依赖注入(DI)
属性注入有两种方式
- 通过构造方法注入
- 通过setter方法注入
setter方法注入
- 使用setter方法注入就一定要保证我们的Bean中有setter方法
- 并且setter的方法一定要符合规
setXxx的方式,Xxx是属性名首字母大写 - 因为注入的时候是通过setter方法的名称来获取属性名称注入的,一旦setter方法不符合就会注入失败
通过xml配置为user注入属性
<bean id="user" class="com.wcy.pojo.User">
<property name="id" value="1"/>
<property name="name" value="wcy"/>
</bean>
property标签是通过setter方法注入name是属性的名称value是属性的值
这样就为我们的user对象注入了属性
这样只是基本属性的注入,其他的一些特殊的属性如何注入呢?
引用类型注入
public class Teacher {
private Integer id;
private String name;
//setter...
}
为User类中添加一个Teacher类型的属性,并且添加setter方法
private Teacher teacher;
通过外部bean注入
<bean id="teacher" class="com.wcy.pojo.Teacher"></bean>
<bean id="user" class="com.wcy.pojo.User">
<property name="teacher" ref="teacher"/>
</bean>
首先我们注册了一个Teacher对象
在user的属性注入中,使用了ref="teacher"来引用我们注册的Teacher对象,ref属性后面的值就是我们注册的Teacher对象的id值
通过内部bean注入
<property name="teacher">
<bean class="com.wcy.pojo.Teacher">
<property name="id" value="1"/>
<property name="name" value="王老师"/>
</bean>
</property>
直接在property标签内注册一个bean来注入
级联赋值
<bean id="teacher" class="com.wcy.pojo.Teacher">
<property name="id" value="1"/>
<property name="name" value="王老师"/>
</bean>
<bean id="user" class="com.wcy.pojo.User">
<property name="teacher" ref="teacher"/>
</bean>
在外部bean注入时,可以注册的时候就为引用的对象赋值
另一种为引用对象赋值的方式
<property name="teacher">
<bean class="com.wcy.pojo.Teacher"/>
</property>
<property name="teacher.id" value="1"/>
<property name="teacher.name" value="王老师"/>
通过.来对引用类型属性的属性来赋值
注意:使用这种方法赋值时,需要有getter方法,这里的Teacher类中必须要有对应的getter方法
空值注入
如果说是要注入空字符串,我们可以什么都不写就可以达到目的
<property name="name" value=""/>
如果是要注入null值可以使用null标签来实现
<property name="name">
<null/>
</property>
特殊符号注入
当我们需要注入的属性值中包含特殊符号时,需要特殊处理
例如向name属性中注入值<<wcy>>
使用转义符
<property name="name" value="<<wcy>>">
使用CDATA注入
<property name="name">
<value><![CDATA[<<wcy>>]]></value>
</property>
这两种方式都可以实现特殊符号的注入
map注入
private Map<String, Object> other;
<property name="other">
<map>
<entry key="age" value="12"/>
<entry key="sex" value="男"/>
</map>
</property>
通过使用map标签来为Map类型的属性注入
数组注入
private String[] strings;
<property name="strings">
<array>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</array>
</property>
list注入
private List<String> hobby;
<property name="hobby">
<list>
<value>写代码</value>
<value>撩妹</value>
</list>
</property>
使用list标签来为List类型的属性注入,value标签中写上值
当list中存储的类型为引用类型时
private List<User> userList;
<property name="userList">
<list>
<ref bean="user1"/>
<ref bean="user2"/>
<ref bean="user3"/>
</list>
</property>
<bean id="user1" class="com.wcy.pojo.User"/>
<bean id="user2" class="com.wcy.pojo.User"/>
<bean id="user3" class="com.wcy.pojo.User"/>
list标签中用ref来引用对象
或者直接在list标签中使用bean标签
<property name="userList">
<list>
<bean id="user1" class="com.wcy.pojo.User">
<property name="id" value="1"/>
</bean>
<bean id="user2" class="com.wcy.pojo.User">
<property name="id" value="2"/>
</bean>
<bean id="user3" class="com.wcy.pojo.User">
<property name="id" value="3"/>
</bean>
</list>
</property>
set集合注入
private Set<Integer> set;
<property name="set">
<set value-type="java.lang.Integer">
<value>1</value>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
提取公共集合
首先需要导入util名称空间
<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"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
</beans>
写一个公共的list
<util:list id="userList">
<ref bean="user2"/>
<ref bean="user3"/>
<ref bean="user4"/>
</util:list>
使用这个公共的list
为userList属性注入这个公共的list
<bean id="teacher1" class="com.wcy.pojo.Teacher">
<property name="userList" ref="userList"/>
</bean>
其他类型也是如此
private Map<String, User> userMap;
<util:map id="userMap">
<entry key="1" value-ref="user2"/>
<entry key="2" value-ref="user3"/>
<entry key="3" value-ref="user4"/>
</util:map>
<bean id="teacher1" class="com.wcy.pojo.Teacher">
<property name="userMap" ref="userMap"/>
</bean>
properties注入
private Properties properties;
<property name="properties">
<props>
<prop key="username">wcy666</prop>
<prop key="password">123456</prop>
</props>
</property>
构造方法注入
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
使用构造方法注入属性之后,创建对象也是通过该有参构造来创建
通过构造方法中参数的名称来注入
<bean id="user2" class="com.wcy.pojo.User">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="wcy"/>
</bean>
通过构造方法中参数的位置索引来注入
<bean id="user3" class="com.wcy.pojo.User">
<constructor-arg index="0" value="2"/>
<constructor-arg index="1" value="wcy"/>
</bean>
通过构造方法中参数的类型来注入
<bean id="user4" class="com.wcy.pojo.User">
<constructor-arg type="java.lang.Integer" value="1"/>
<constructor-arg type="java.lang.String" value="wcy"/>
</bean>
使用构造方法注入其他类型的属性时,和使用setter方法相同
p名称空间注入
使用p名称空间注入时,需要在配置文件头部导入p名称空间的规范
xmlns:p="http://www.springframework.org/schema/p"
<bean id="user7" class="com.wcy.pojo.User" p:name="wcy" p:id="1" p:teacher-ref="teacher"/>
使用p:属性名的方式来注入属性
这种注入方法也是通过setter方法来注入属性的
FactoryBean
Spring 有两种类型的 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
-
普通 bean:在配置文件中定义 bean 类型就是返回类型
-
工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
BeanFactory是IOC容器的基本实现方式,FactoryBean是Spring中的一种Bean类型
普通Bean
编写一个类MyBeanFactory
public class MyFactoryBean{
}
再去配置文件中注册
<bean id="myFactoryBean" class="com.wcy.pojo.MyFactoryBean"/>
获取这个Bean,此时获取的是MyFactoryBean类型的对象,这就是普通类型的bean
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
MyFactoryBean myFactoryBean = context.getBean("myFactoryBean", MyFactoryBean.class);
System.out.println(myFactoryBean);
}
FactoryBean
继续使用刚刚的类,实现Factory接口
public class MyFactoryBean implements FactoryBean<User> {
/**
* 通过该方法返回所需的对象
*/
@Override
public User getObject() throws Exception {
return new User(1, "wcy");
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
此时这个类就是FactoryBean
- 泛型指定这个FactoryBean返回什么类型的对象
- 通过getObject方法来返回这个对象
注册
<bean id="myFactoryBean" class="com.wcy.pojo.MyFactoryBean"/>
此时获取的就是一个User对象了
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
User user = context.getBean("myFactoryBean", User.class);
System.out.println(user);
}
Bean的作用域
spring中默认情况下,注册的对象都是单例的,全局唯一
<bean id="user" class="com.wcy.pojo.User"/>
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("application1.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1==user2); // true
}
通过scope属性可以更改bean的作用域,socpe的常用属性值有两个
singleton单例模式,对象是全局唯一的,这也是默认值prototype多实例模式,每一次获取的对象都是不一样的
bean的scope属性为singleton和prototype的区别
- singleton 单实例,prototype 多实例
- scope 值是
singleton时,加载 spring 配置文件时候就会创建单实例对象 - scope 值是
prototype时,不是在加载 spring 配置文件时候创建就对象,而是在调用getBean方法时才创建对象
<bean id="user" class="com.wcy.pojo.User" scope="prototype"/>
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("application1.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1==user2); // false
}
Bean的生命周期
生命周期是从对象创建到对象销毁的过程
bean的生命周期
- 通过构造器创建 bean 实例(构造方法)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- bean 的使用(对象获取到了)
- 容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
创建一个Blog类查看bean的生命周期
public class Blog {
private Integer id;
private String title;
public Blog() {
System.out.println("无参构造方法");
}
public Blog(Integer id, String title) {
this.id = id;
this.title = title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("set方法");
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
/**
* 初始化方法 名字没有要求
*/
public void init(){
System.out.println("初始化方法");
}
/**
* 销毁方法 名字没有要求
*/
public void destroy(){
System.out.println("销毁方法");
}
@Override
public String toString() {
return "使用了Blog{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
注册bean
<bean id="blog" class="com.wcy.pojo.Blog" init-method="init" destroy-method="destroy">
<property name="id" value="1"/>
<property name="title" value="java入门"/>
</bean>
获取bean,并且使用
@Test
public void test1(){
// 由于Application接口中没有close方法,所以使用它的实现类ClassPathXmlApplicationContext
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application1.xml");
Blog blog = context.getBean("blog", Blog.class);
System.out.println(blog);
context.close();
}
执行结果

和我们说的生命周期顺序符合
当我们的bean有后置处理器时,生命周期变为了7步
- 通过构造器创建 bean 实例(构造方法)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 把 bean 实例传递 bean 后置处理器的方法
postProcessBeforeInitialization - 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 把 bean 实例传递 bean 后置处理器的方法
postProcessAfterInitialization - bean 的使用(对象获取到了)
- 容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
添加bean的后置处理器,编写一个类实现BeanPostProcessor接口
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化方法之前执行处理");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化方法之后执行处理");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
注册这个后置处理器,后置处理器会在该容器中所有的bean的初始化方法前后执行
<bean id="myBeanPostProcessor" class="com.wcy.pojo.MyBeanPostProcessor"/>
执行结果

自动装配
自动装配是根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
使用 bean 标签属性 autowire,配置自动装配
autowire 属性有两个值:
byName根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样byType根据属性类型注入
public class Student {
private Teacher teacher;
//getter and setter
}
public class Teacher {
private String name;
//getter and setter
}
byName自动装配
使用byName自动装配时,spring会根据属性的名字取查找容器中注册的bean的id值,如果有一样的就会注入
<bean id="teacher" class="com.wcy.autowire.Teacher">
<property name="name" value="王老师"/>
</bean>
<bean id="student" class="com.wcy.autowire.Student" autowire="byName"/>
- 使用byName注入时,如果名字匹配上了但是类型不匹配会注入失败
- 根据属性名查询,其实也是通过setter方法中的名字来查找的,如果setter方法中名字不匹配,就会注入失败
byType自动装配
使用byName自动装配时,spring会根据属性的类型取查找容器中注册的bean的类型,如果有一样的就会注入
<bean id="teacher" class="com.wcy.autowire.Teacher">
<property name="name" value="王老师"/>
</bean>
<bean id="student" class="com.wcy.autowire.Student" autowire="byType"/>
- 使用byType自动装配时,对属性的名字没有要求
- 但是同类型的对象必须要唯一,如果不唯一就会报错,这里的例子中Teacher类型的对象必须唯一
外部属性文件引用
一般情况下,我们需要配置数据源时,把值固定在xml文件中,不利于更改
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="name" value="root"/>
<property name="password" value="123456"/>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/wcy"/>
</bean>
在spring中,可以通过引入外部配置文件的方法来动态的配置
首先需要导入context名称空间
<?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">
</beans>
类路径下编写一个配置文件db.properties
jdbc.username=root
jdbc.password=1234
jdbc.url=jdbc:mysql://localhost:3306/wcy
jdbc.driver=com.mysql.jdbc.Driver
在xml中引用这个外部配置文件
<!--引用外部配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
注解实现Bean管理
什么是注解
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值..)
(2)使用注解,注解可以作用在类上面,方法上面,属性上面
(3)使用注解目的:简化 xml 配置
Spring 针对 Bean 管理中创建对象提供的
@Component@Service@Controller@Repository
上面四个注解功能都可以用来创建 bean 实例,通常为了区分业务模块会在不同的层使用不同的注解
使用spring的注解功能需要导入spring-aop依赖
spring-aop-5.2.18.RELEASE.jar
创建对象
编写一个UserService类
@Service(value = "userService")
public class UserService {
public void sayHello(){
System.out.println("hello world!");
}
}
@Service(value = "userService")的作用就相当于<bean id="userService" class="com.wcy.service.UserService"/>
- 注解的value属性就和id一样,默认值是类名的首字母小写
配置文件
首先要引入context名称空间
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
开启注解扫描base-package的值就是需要扫描的包
<!--
如果想精确扫多个包,可以使用逗号隔开包名
<context:component-scan base-package="com.wcy.dao,com.wcy.service"/>
-->
<context:component-scan base-package="com.wcy"/>
获取对象
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.sayHello();
}
注解扫扫描配置
指定要扫描那些注解
扫描@Controller注解和@Service注解
<context:component-scan base-package="com.wcy" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
use-default-filters:是否使用默认的filters,默认值为true,扫描创建Bean的所有的注解,false就是不使用,不会扫描任何注解context:include-filter: 配置需要扫描的注解,type是类型即注解,expression是注解的的全类名
指定不扫描那些注解
不扫描@Controller注解
<context:component-scan base-package="com.wcy">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
context:exclude-filter: 配置不需要扫描的注解,type是类型即注解,expression是注解的的全类名
属性注入
首先创建一个UserService和UserDao,注册为Bean
@Repository(value = "userDao")
public class UserDao {
public void sayHello(){
System.out.println("hello dao!");
}
}
UserService中需要UserDao的实例
@Service(value = "userService")
public class UserService {
UserDao userDao;
public void sayHello(){
System.out.println("hello service!");
userDao.sayHello();
}
}
@Autowired注入
@Autowired是通过属性的类型(byType)来进行自动注入的@Autowired不需要setter方法就可以注入@Autowired也可以放在setter方法上
需要向UserService中注入UserDao只需要加上注解即可
@Autowired
UserDao userDao;
或者说放在setter方法上同样可以注入,没有setter方法也可以注入
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Qualifier注入
@Qualifier是根据名称(byName)进行注入@Qualifier需要和@Autowired一起使用- 不需要setter方法
使用该方法一样可以注入
@Autowired
@Qualifier("userDao")
UserDao userDao;
@Resource注入
@Resource可以根据类型注入,也可以根据名称注入@Resource不是spring提供的注解,是javax包下的
通过类型注入
@Resource
UserDao userDao;
通过名称注入
@Resource(name = "userDao")
UserDao userDao;
@Value注入
- @Value注解用于注入普通类型的属性
- 不需要setter方法
@Value("1")
private Integer id;
@Value("wcy")
private String name;
纯注解开发
纯注解开发就是完全的舍弃配置文件,通过注解的方式来开发
首先需要有一个配置类
@Configuration
@ComponentScan(basePackages = {"com.wcy"})
public class SpringConfig {
}
@Configuration表示这是一个配置类@ComponentScan指定扫描的包
上面的配置就相当于在配置文件中的
<context:component-scan base-package="com.wcy"/>
但是现在我们完全舍弃了配置文件
获取配置的方式也发生改变
@Test
public void test2(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.sayHello();
}
- 通过使用
AnnotationConfigApplicationContext类,传入我们的配置类的类对象来获取容器 - 获取Bean的方式依然一样
- 只有配置文件变为了配置类,其他的全部一样
结果和使用配置文件的作用一样
IOC的内容就基本告一段落~

浙公网安备 33010602011771号