[Spring框架]IOC容器
一、IOC容器
1. 什么是IOC?
- 把对象创建和对象之间的调用过程,交给Spring进行管理
- 使用目的:降低耦合度
2. IOC底层?
- xml解析
- 工厂模式
- 反射
IOC是一个容器,本质上就是一个对象工厂,在里面可以读取xml配置文件,通过反射创建对象
3. Spring提供的IOC容器实现的两种方式(两个接口)
-
BeanFactory接口:IOC容器基本实现是Spring内部接口的使用接口,面向 Spring 本身,加载配置文件时候不会创建对象,在获取对象时才会创建对象
-
ApplicationContext接口:BeanFactory接口的子接口,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)
ApplicationContext 的主要实现类:
- ClassPathXmlApplicationContext:从类路径下(bin目录下)加载配置文件
- FileSystemXmlApplicationContext: 从文件系统中加载配置文件
二者区别
- BeanFactory 才是 Spring 容器中的顶层接口,ApplicationContext 是它的子接口。
- BeanFactory 和 ApplicationContext 的区别:
- 创建对象的时间点不一样
- ApplicationContext:只要一读取配置文件,默认情况下就会创建对象
- BeanFactory:什么使用什么时候创建对象
二、IOC容器-Bean管理
什么是Bean?
Spring Bean is the key concept or backbone of the Spring Framework. Spring Bean is the object whose life-cycle managed by the Spring IoC. It is important to understand it before we work with the Spring Framework. In simple words Spring Bean is the core building block for any Spring application.
——from https://www.javadevjournal.com/spring/what-is-a-spring-bean/
Spring Bean是由Spring IoC管理其生命周期的对象。
简单地说,Spring Bean是任何Spring应用程序的核心构建块。
1. IOC操作Bean管理
- 创建对象
- 注入属性
2. 基于XML配置文件创建对象
<!--1 配置User对象创建-->
<bean id="user" class="com.w.spring.User"></bean>
- 在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
- 在bean标签有很多属性:
- id属性:给对象在容器中提供一个唯一标识,用于获取对象
- class属性:指定类的全限定类名。用于反射创建对象,默认情况下通过无参构造函数
- scope:指定对象的作用范围
Bean的作用范围和生命周期
- 单例对象:scope="singleton"
- 一个Spring容器只有一个对象的实例。它的作用范围就是整个引用
- 生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
- 多例对象:scope="prototype"
- 每次访问对象时,都会重新创建对象实例
- 生命周期:
- 对象出生:当使用对象时,创建新的对象实例。不使用就不创建
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
- 实例
在业务层中添加:
public UserServiceImpl(){
System.out.println("创建对象实例");
}
public void init(){
System.out.println("对象被创建了");
}
public void destory(){
System.out.println("对象被销毁了");
}
在applicationContext.xml中
<bean id="userservice" class="com.w.service.UserServiceImpl" scope="singleton" init-method="init" destroy-method="destory">
</bean>
测试类中:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("==================================");
IUserService userService = (IUserService) context.getBean("userservice");
运行结果:
在===========上面时,对象就已经被创建了,且被init。
在main执行完毕后,线程结束,容器销毁,来不及打印,如果需要看到destroy的过程,需要手动销毁。
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("==================================");
IUserService userService = (IUserService) context.getBean("userservice");
context.close();
改成多例模式:
<bean id="userservice" class="com.w.service.UserServiceImpl" scope="prototype" init-method="init" destroy-method="destory">
</bean>
结果:
对象是懒加载的方式,即在用到的时候才会创建,而且我们手动关闭容器的时候也不会调用destory方法,原因很简单,多例对象的死亡是由java垃圾回收器回收的,不受容器管理。
3. 实例化Bean的三种方式
1. 默认使用无参构造
<bean id="userservice" class="com.w.service.UserServiceImpl"></bean>
2. 使用静态工厂
用 StaticFactory 类中的静态方法 createUserService创建对象,并存入 spring 容器
factory-method 属性:指定生产对象的静态方法
<bean id="userservice" class="com.w.util.StaticFactory" factory-method="createUserService">
</bean>
package com.woniu.util;
public class StaticFactory {
public static IUserService createUserService() {
System.out.println("静态工厂创建");
return new UserServiceImpl();
}
}
3. 使用实例工厂
先把工厂的创建交给 spring 来管理,然后使用工厂的 bean 来调用里面的方法
package com.woniu.util;
public class FactoryBean {
public IUserService createUserService() {
System.out.println("实例工厂创建");
return new UserServiceImpl();
}
}
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
<bean id="instanceFactory" class="com.w.util.FactoryBean"></bean>
<bean id = "userservice" factory-bean="instanceFactory" factory-method="createUserService">
</bean>
4. FactoryBean和BeanFactory
1. 什么是FactoryBean?
FactoryBean是spring规定的一个接口,只要是这个接口的实现类,spring都认为是一个工厂。当我们创建一个工厂对象时,实际上是调用重写的方法getObject()得到一个真正的bean,而不是工厂bean。
举例:
- 定义一个类
public class MyBean {
}
- 定义一个实现FactoryBean接口的工厂类用以生产MyBean对象
public class MyFactoryBean implements FactoryBean<MyBean> {
@Override
public MyBean getObject() throws Exception {
System.out.println("Mybean初始化时间" + LocalDateTime.now());
return new MyBean();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
- 在配置文件中:
<bean id="myFactoryBean" class="com.w.FactoryBean.MyFactoryBean"></bean>
- 测试:
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("============================");
for(int i = 0;i<5;i++) {
Object bean = ac.getBean("myFactoryBean"); //获取工厂实例用 &myFactoryBean
System.out.println(bean);
}
}
结果是造了5个Bean对象
2. BeanFactory和FactoryBean的区别与联系?
- BeanFactory
- BeanFactory是一个接口,是spring工厂的顶层规范,是IOC容器的核心接口
- 它相当于一个工厂,内部提供了获取Bean、判断Bean是否单例、获取Bean别名等一系列的方法
- spring内部有该接口的多个实现类,通过这些实现类我们可以从xml文件中解析Bean标签、创建Bean,或者从注解中获取信息并创建Bean
- FactoryBean
- FactoryBean是创建自定义Bean的一种方式
- 在spring中一般通过反射获取对象,获取对象所需要的信息可以通过标签进行配置,也可以通过注解扫描的方式获取Bean
- 我们可以通过配置FatcoryBean实现,自己写创建Bean的逻辑
- 在spring获取bean的时候,如果当前bean是FactoryBean的类型,那么就会调用getObject返回实际要创建的类型
简而言之:
- BeanFactory用于管理Bean,FactoryBean本身就是一个Bean
- BeanFactory实现IOC, FactoryBean实现自定义Bean
- 设计模式上来说,都是工厂,但职能不同
5. 基于xml配置文件进行依赖注入
依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
简单来说就是对象之间的依赖关系不再需要使用者去维护,而是经过IoC的管理进行注入
a. 构造函数注入
就是使用类中的构造函数,给成员变量赋值
举例:
分别定义接口和实现类
public interface IAccountService {
public void saveAccount();
}
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println(name + "-" + age + "-" + birthday);
}
}
在xml文件中使用constructor-arg标签实现依赖注入
<bean id="birhday" class="java.util.Date" />
<bean id ="accountService" class="com.w.service.AccountServiceImpl">
<constructor-arg index="0" value="zhangsa"></constructor-arg>
<constructor-arg index="1" value="20"></constructor-arg>
<constructor-arg index="2" ref="birhday"></constructor-arg>
</bean>
constructor-arg标签有以下属性:
index:所对应的参数在参数列表中的索引
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称
value:基本数据类型和String类型的属性的值
ref:其他bean类型
如果使用ref,则需要单独在配置文件中配置相应的bean
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IAccountService accountService = (IAccountService) context.getBean("accountService");
accountService.saveAccount();
b. set方法注入
set方法需要在类中提供成员变量的set方法
在xml文件中使用property标签实现依赖注入
<bean id="accountService" class="com.woniu.service.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="birhday"></property>
</bean>
property标签有以下属性:
name:类中setXXX()的XXX部分
value:基本数据类型和String类型的属性的值
ref:其他bean类型
如果使用ref,则需要单独在配置文件中配置相应的bean
c. p名称空间注入数据
通过在xml中导入p名称空间,使用 p:propertyName注入。
本质上仍然是通过set方法注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="birhday" class="java.util.Date" />
<bean id="accountService" class="com.w.service.AccountServiceImpl" p:name="zhangsa" p:age="20" p:birthday-ref="birhday">
</bean>
</beans>
补充1:注入集合属性
List 结构的:array,list,set
Map 结构的:map,entry,props,prop
public class CollectionService {
private List<User> lists;
private Map<String,User> maps;
private Map<String,String> maps2;
private Set<User> sets;
private Properties props;
public void setLists(List<User> lists) {
this.lists = lists;
}
public List<User> getLists() {
return lists;
}
public Map<String, User> getMaps() {
return maps;
}
public void setMaps(Map<String, User> maps) {
this.maps = maps;
}
public Map<String, String> getMaps2() {
return maps2;
}
public void setMaps2(Map<String, String> maps2) {
this.maps2 = maps2;
}
public Set<User> getSets() {
return sets;
}
public void setSets(Set<User> sets) {
this.sets = sets;
}
public Properties getProps() {
return props;
}
public void setProps(Properties props) {
this.props = props;
}
private IUserDao userDao;
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
public void save(){
for(User user : lists){
System.out.println(user.getId()+user.getName());
}
for(User user : sets){
System.out.println(user.getId()+user.getName());
}
System.out.println("===========map=====================");
for(Map.Entry<String,User> entry:maps.entrySet()){
System.out.println(entry.getKey()+entry.getValue().getName());
}
System.out.println("============map2================");
for(Map.Entry<String,String> entry:maps2.entrySet()){
System.out.println(entry.getKey()+entry.getValue());
}
System.out.println("==============props===================");
for(Map.Entry<Object,Object> entry:props.entrySet()){
System.out.println(entry.getKey()+": "+entry.getValue());
}
}
}
xml配置文件:
<bean id="userdao" class="com.w.dao.UserDaoImpl" />
<bean id="user1" class="com.w.entity.User">
<property name="id" value="2" />
<property name="name" value="list" />
</bean>
<bean id="collectionService" class="com.w.service.CollectionService">
<property name="userDao">
<ref bean="userdao" />
</property>
<property name="lists">
<list>
<bean class="com.w.entity.User">
<property name="id" value="1" />
<property name="name" value="zhangsan" />
</bean>
<ref bean = "user1" />
</list>
</property>
<property name="sets">
<list>
<bean class="com.w.entity.User">
<property name="id" value="1" />
<property name="name" value="zhangsan" />
</bean>
<ref bean = "user1" />
</list>
</property>
<property name="maps">
<map>
<entry key="AA" value-ref="user1">
</entry>
<entry key="BB">
<bean class="com.w.entity.User">
<property name="id" value="1"/>
<property name="name" value="List" />
</bean>
</entry>
</map>
</property>
<property name="maps2">
<map>
<entry key="AAA" value="zhangsan"></entry>
<entry key="BBB" value="list"></entry>
</map>
</property>
<property name="props">
<props>
<prop key="AAA">aa</prop>
<prop key="BBB">bb</prop>
</props>
</property>
</bean>
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
CollectionService collectionService = (CollectionService) context.getBean("collectionService");
collectionService.save();
补充2:自动装配
自动装配是使用Spring满足bean依赖的一种方式,Spring会在应用中为某个bean寻找其依赖的bean。
自动装配功能具有四种模式,分别是 no,byName,byType和constructor。
- XML配置中的默认自动装配模式为no
- Java配置中的默认自动装配模式是byType
自动装配模式
- no
该选项是spring框架的默认选项,表示自动装配为关闭状态OFF。我们必须在bean定义中使用标签显式设置依赖项。 - byName
此选项启用基于bean名称的依赖项注入。在Bean中自动装配属性时,属性名称用于在配置文件中搜索匹配的Bean定义。如果找到这样的bean,则将其注入属性。如果找不到这样的bean,则不会赋值。 - byType
此选项支持基于bean类型的依赖项注入。在bean中自动装配属性时,属性的类类型用于在配置文件中搜索匹配的bean定义。如果找到这样的bean,就在属性中注入它。如果没有找到这样的bean,就会引发一个错误。 - constructor
通过构造函数自动装配与byType相似,仅适用于构造函数参数。在启用了自动装配的bean中,它将查找构造函数参数的类类型,然后对所有构造函数参数执行自动装配类型。请注意,如果容器中没有一个完全属于构造函数参数类型的bean,则会引发致命错误。
<bean id="accountService" class="com.w.service.impl.AccountServiceImpl" autowire="byName">
</bean>
<bean id="accountService" class="com.w.service.impl.AccountServiceImpl" autowire="byType">
</bean>
6. 基于注解创建对象
1. @Component注解
- 作用:相当于:
<bean id="" class="">
- 属性:value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名,首字母小写。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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:component-scan:专门扫描含有@Component注解的类,自动将其作为bean
base-package:要扫描包的路径,包含子包,com.woniu 表示子包下的所有类定义注解都有效
注解扫描配置的时候,会自动开启注解功能
-->
<context:component-scan base-package="com.woniu"/>
</beans>
2. @Controller @Service @Repository
他们三个注解都是@Component的衍生注解
@Controller :一般用于表现层的注解。
@Service :一般用于业务层的注解。
@Repository :一般用于持久层的注解。
7. 基于注解注入数据
1. @Autowired
作用:自动按照类型注入。当使用注解注入属性时,set方法可以省略。当有多个类型匹配时或者找不到就报错。
2. @Qualifier
作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。
3. @Resource
JSR-250标准(基于jdk),单独使用@Resource注解,表示先按照名称注入,会到spring容器中查找userDao的名称,对应<bean id="">
,id的属性值,如果找到,可以匹配。
如果没有找到,则会按照类型注入,会到spring容器中查找IUserDao的类型,对应<bean class="">
,class的属性值,如果找到,可以匹配,如果没有找到会抛出异常。