Spring IoC 概述
正转:若要使用某个对象,需要自己去负责对象的创建。
反转:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架。
Spring IoC 容器的设计
Spring IoC 容器的设计主要是基于以下两个接口:
- BeanFactory
- ApplicationContext
其中 ApplicationContext 是 BeanFactory 的子接口之一,换句话说:BeanFactory 是 Spring IoC 容器所定义的最底层接口,而 ApplicationContext 是其最高级接口之一,并对 BeanFactory 功能做了许多的扩展,所以在绝大部分的工作场景下,都会使用 ApplicationContext 作为 Spring IoC 容器。
BeanFactory接口
// bean的获取
bean = (Bean) factory.getBean("beanName"); //按照 bean的名字拿bean。这种方法不太安全,IDE 不会检查其安全性(关联性)
bean = (Bean) factory.getBean(Bean.class); //按照类型拿bean。要求在 Spring 中只配置了一个这种类型的实例,否则报错
bean = (Bean) factory.getBean("beanName", Bean.class); //按照名字和类型拿 bean(推荐)
// bean的检查
boolean = factory.containsBean("beanName"); //容器是否包含这个bean
boolean = factory.isTypeMatch("beanName", Bean.class) // bean类型是否匹配
boolean = factory.isSingleton("beanName"); //bean是否单例模式
boolean = factory.isPrototype("beanName"); //bean是否prototype模式:
/**
*1、prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean方法)时都会创建一个新的Bean实例;
*2、对于具有prototype作用域的Bean,有一点很重要,即Spring不能对该Bean的整个生命周期负责。具有prototype作用域的Bean创建后交由调用者负责销毁对象回收资源。
**/
ApplicationContext接口
ApplicationContext 接口继承BeanFactory接口,但扩展了许许多多的接口,因此它的功能十分强大,在实际应用中常常会使用到的是 ApplicationContext 接口,ApplicationContext 的方法和功能较多。
- 常见实现类
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");
/**
*XmlWebApplicationContext:需要在Web的环境下才可以运行,所以运行起来比较特殊
**/
XmlWebApplicationContext ac = new XmlWebApplicationContext(); // 这时并没有初始化容器
ac.setServletContext(servletContext); // 需要指定ServletContext对象
ac.setConfigLocation("/WEB-INF/applicationContext.xml"); // 指定配置文件路径,开头的斜线表示Web应用的根目录
ac.refresh(); // 初始化容器
- 使用示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!-- 通过 xml 方式装配 bean -->
<bean name="source" class="pojo.Source">
<property name="fruit" value="橙子"/>
<property name="sugar" value="多糖"/>
<property name="size" value="超大杯"/>
</bean>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Source source = (Source) context.getBean("source", Source.class);
System.out.println(source.getFruit());
System.out.println(source.getSugar());
System.out.println(source.getSize());
=========================此处内容分隔符=========================
=========================此处内容分隔符=========================
=========================此处内容分隔符=========================
Bean的定义和初始化
-
Resource 定位
Spring IoC 容器先根据开发者的配置,进行资源的定位,在 Spring 的开发中,通过 XML 或者注解都是十分常见的方式,定位的内容是由开发者提供的。 -
BeanDefinition 的载入
这个时候只是将 Resource 定位到的信息,保存到 Bean 定义(BeanDefinition)中,此时并不会创建 Bean 的实例 -
BeanDefinition 的注册
这个过程就是将 BeanDefinition 的信息发布到 Spring IoC 容器中。注意:此时仍然没有对应的 Bean 的实例。 -
Bean的初始化
做完了以上 3 步,Bean 就在 Spring IoC 容器中被定义了,而没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给 Bean,那么它还不能完全使用。
对于初始化和依赖注入,Spring Bean 还有一个配置选项——【lazy-init】,其含义就是是否初始化 Spring Bean。在没有任何配置的情况下,它的默认值为 default,实际值为 false,也就是 Spring IoC 默认会自动初始化 Bean。如果将其设置为 true,那么只有当我们使用 Spring IoC 容器的 getBean 方法获取它时,它才会进行 Bean 的初始化,完成依赖注入。
Bean的生成
bean的默认id号或者名字
- 组件扫描(Component)的情况:默认的id号或者bean的name是类名的首字母小写。
- 显示配置bean(Configuration-Bean)时:这个时候bean默认的名字是与方法名相同
- 在配置文件xml中的配置:<bean class="com.qls.Hello"/>这个bean的id号默认是com.qls.Hello#0即(包名.类名#自然数)
组件扫描生成bean
-
xml标签
配置完<context:component-scan>这个标签后,spring就会去自动扫描base-package对应的路径或者该路径的子包下面的java文件,如果扫描到文件中带有@Component,@Service,@Repository,@Controller等这些注解的类,则把这些类注册为bean。
/**
* base-package属性告诉spring要扫描的包
* use-default-filters=”false”表示不要使用默认的过滤器,默认过滤器会扫描包含@Service,@Component,@Repository,@Controller注解修饰的类
* <context:excluce-filter> 来按照规则排除某些包的扫描
* <context:incluce-filter> 来按照规则只包含某些包的扫描
**/
context:component-scan base-package="com.sparta.trans.controller" use-default-filters="false">
<context:include-filter type="regex" expression="com\.sparta\.trans\.[^.]+(Controller|Service)"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
filter标签的type和表达式说明如下
-
@ComponentScan注解
@ComponentScan 的 useDefaultFilters 属性,该属性默认值为 true,也就是说 spring 默认会自动发现被 @Component、@Repository、@Service 和 @Controller 标注的类,并注册进容器中。要达到只包含某些包的扫描效果,就必须将这个默认行为给禁用掉(在 @ComponentScan 中将 useDefaultFilters 设为 false 即可)
/**
* 创建一个配置类,在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类
* 配置类包括@Component、@Repository、@Service 和 @Controller
**/
@ComponentScan
public class BeanConfig {}
/**
* value指定了要扫描的包,默认扫描包括@Component、@Repository、@Service 和 @Controller注解的类
* 使用 excludeFilters 来按照规则排除某些包的扫描
* 使用 includeFilters 来按照规则只包含某些包的扫描
**/
@ComponentScan(value = "io.mieux",
excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Controller.class})})
includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
useDefaultFilters = false
public class BeanConfig {}
/**
* 如果使用的 jdk8,则可以直接添加多个 @ComponentScan 来添加多个扫描规则,但是在配置类中要加上 @Configuration 注解,否则无效
* 也可以使用 @ComponentScans 来添加多个 @ComponentScan,从而实现添加多个扫描规则。同样,也需要加上 @Configuration 注解,否则无效
**/
@ComponentScan(value = "io.mieux.controller")
@ComponentScan(value = "io.mieux.service")
@ComponentScans(value = {@ComponentScan(value = "io.mieux.controller"), @ComponentScan(value = "io.mieux.service")})
@Configuration
public class BeanConfig {}
使用@Bean生成bean
-
问题
以上都是通过 @Component 注解来装配 Bean ,并且只能注解在类上,当你需要引用第三方包的(jar 文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入 @Component 注解,让它们变成开发环境中的 Bean 资源。 -
解决方案
1.自己创建一个新的类来扩展包里的类,然后再新类上使用 @Component 注解,但这样很 low
2.使用 @Bean 注解,注解到方法之上,使其成为 Spring 中返回对象为 Spring 的 Bean 资源。
Bean的装配(注入)
byType的注入是指通过类的包名+类名来确定bean的类型,如果一个类型有多个bean则会报错。
byName就是通过Bean的id或者name来注入的。例如:代码autowire="byName"意思是通过id="userDao"来查找Bean中的userDao对象
- xml配置方式
<-- 基于构造函数的依赖注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>
<-- 基于设置函数setter的依赖注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
</bean>
<-- 基于设置函数setter的内部依赖注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker">
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker"/>
</property>
</bean>
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>
<-- 基于构造函数的自动装配(实质是byType自动装配) -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="constructor">
<constructor-arg value="Generic Text Editor"/>
</bean>
<-- 基于设置函数setter的byName自动装配 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="byName">
<property name="name" value="Generic Text Editor" />
</bean>
<-- 基于设置函数setter的byType自动装配 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="byType">
<property name="name" value="Generic Text Editor" />
</bean>
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>
- 注解方式
@Required 注释应用于bean属性的setter方法上,表明在注入bean时这个bean必须存在。
@Resource 此非spring的注解,应用在field和bean属性的setter方法上,如果找不到会报错。
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为按照byType方式进行装配。
@Autowired 注解应用在constructor、bean、setter方法上,是按照类型(byType)装配依赖对象,
默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。
如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用