4.1 BeanFactory和ApplicationContext的关系

  1. IOC实现beans.xml和BeanFactory

spring ioc讲解说明.png

  1. ApplicationContext接口(容器的高级接口)是BeanFactory接口(基础容器)的一个子接口,所以ApplicatonContext在BeanFactory的接口上定义了更多的功能,比如资源加载、国际化支持等等。这个设计正是Spring容器设计的优雅之处,顶层接口BeanFactory只定义了少数的基础功能和基础规范,子接口作为扩展,实现类只需要根据需求实现适合的接口,如果把所有功能都定义在顶层接口,那么实现类不需要的功能也要一一实现,该类就变得冗余复杂了。

BeanFactory结构树.png

4.2 启动IOC容器的方式

  1. JAVA环境启动IOC容器的方式
  • ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
  • AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
  1. Web环境下启动IoC容器方式
  • 从xml启动容器

    1. <!DOCTYPE web-app PUBLIC
    2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    3. "http://java.sun.com/dtd/web-app_2_3.dtd" >
    4. <web-app>
    5. <display-name>Archetype Created Web Application</display-name>
    6. <!--配置Spring ioc容器的配置⽂件-->
    7. <context-param>
    8. <param-name>contextConfigLocation</param-name>
    9. <param-value>classpath:applicationContext.xml</param-value>
    10. </context-param>
    11. <!--使⽤监听器启动Spring的IOC容器-->
    12. <listener>
    13. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    14. </listener>
    15. </web-app>
  • 从配置类启动容器

    1. <!DOCTYPE web-app PUBLIC
    2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    3. "http://java.sun.com/dtd/web-app_2_3.dtd" >
    4. <web-app>
    5. <display-name>Archetype Created Web Application</display-name>
    6. <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
    7. <context-param>
    8. <param-name>contextClass</param-name>
    9. <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    10. </context-param>
    11. <!--配置启动类的全限定类名-->
    12. <context-param>
    13. <param-name>contextConfigLocation</param-name>
    14. <param-value>com.atm.edu.SpringConfig</param-value>
    15. </context-param>
    16. <!--使⽤监听器启动Spring的IOC容器-->
    17. <listener>
    18. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    19. </listener>
    20. </web-app>

    4.3 纯XML模式

    4.3.1 编写applicationContext.xml

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. https://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <!--id标识对象,class是类的全限定类名-->
    7. <bean id="accountDao"
    8. class="com.atm.dao.JdbcAccountDaoImpl">
    9. <property name="ConnectionUtils" ref="connectionUtils"/>
    10. </bean>
    11. <bean id="transferService"
    12. class="com.atm.service.TransferServiceImpl">
    13. <!--set+ name 之后锁定到传值的set⽅法了,通过反射技术可以调⽤该⽅法传⼊对应的值-->
    14. <property name="AccountDao" ref="accountDao"/>
    15. </bean>
    16. <!--配置新增的三个Bean-->
    17. <bean id="connectionUtils" class="com.atm.utils.ConnectionUtils"/>
    18. <!--事务管理器-->
    19. <bean id="transactionManager" class="com.atm.utils.TransactionManager">
    20. <property name="ConnectionUtils" ref="connectionUtils"/>
    21. </bean>
    22. </beans>

    4.3.2 使用IOC容器

  • 在web开发中,使用web环境下启动ioc容器方式(4.2),编写好配置文件,启动tomcat。

  • 如何从容器中获取bean ```java private TransferService transferService = null ;

@Override public void init() throws ServletException { // 1.通过web工具类获取到容器 WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); // 2.从容器中获取bean transferService = (TransferService) webApplicationContext.getBean(“transferService”); }

  1. <a name="c3TZk"></a>
  2. ## 4.3.2 IOC实例化bean的三种方式
  3. 1. **方式一**:使用无参构造器(推荐),通过全限定类名反射就是调用无参构造器实例化。
  4. 2. 另外两种方法是为了将我们自己new的对象加入到容器里面,比如某个方法的返回值
  5. - **方式二**:使用静态方法
  6. ```java
  7. public class GetBeanFactory {
  8. public static ConnectionUtils getStaticConnectionUtils() {
  9. return new ConnectionUtils();
  10. }
  11. }
  1. <!--需要定位到GetBeanFactory类里的静态方法getStaticConnectionUtils()-->
  2. <bean id="connectionUtils" class="com.atm.factory.GetBeanFactory" factory-method="getStaticConnectionUtils"/>
  • 方式三:实例化方法
    1. public class GetBeanFactory {
    2. public ConnectionUtils getConnectionUtils() {
    3. return new ConnectionUtils();
    4. }
    5. }
    ```xml
  1. <a name="EhrfE"></a>
  2. ## 4.3.3 Bean的作用范围和生命周期
  3. 1. 作用范围的改变
  4. - 在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。
  5. | scope | descrition |
  6. | --- | --- |
  7. | singleton | (默认值)(单例模式)为每个Spring IoC容器将一个bean定义的范围限定为一个对象实例。 |
  8. | prototype | (多例模式)将单个bean定义的范围限定为任意数量的对象实例。 |
  9. | request | 将单个bean定义的范围限定为单个HTTP请求的生命周期;也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的基础上创建的。仅在可感知web的Spring ApplicationContext上下文中有效。 |
  10. | session | 将单个bean定义的范围限定为HTTP会话的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效。 |
  11. | application | 将单个bean定义的范围限定为ServletContext的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效。 |
  12. | websocket | 将单个bean定义的范围限定为WebSocket的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效。 |
  13. - 配置方式
  14. ```xml
  15. <!--使用scope属性配置bean的作用范围-->
  16. <bean id="transferService"
  17. class="com.atm.service.impl.TransferServiceImpl" scope="singleton">
  18. </bean>
  1. 生命周期
  • 单例模式:singleton:
    • 对象出⽣:当创建容器时,对象就被创建了。
    • 对象活着:只要容器在,对象⼀直活着。
    • 对象死亡:当销毁容器时,对象就被销毁了。
    • ⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
  • 多例模式:prototype
    • 对象出⽣:当使⽤对象时,创建新的对象实例。
    • 对象活着:只要对象在使⽤中,就⼀直活着。
    • 对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
    • ⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。

      4.3.4 Bean标签的属性

  1. id属性:⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
  2. class属性:⽤于指定创建Bean对象的全限定类名。
  3. name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔(一般不使用)。
  4. factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。(4.3.2示例)
  5. factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。(4.3.2示例)
  6. scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
  7. init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
  8. destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

    4.3.5 依赖注入的两种方式

  9. set方法注入:就是利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。

    1. public class JdbcAccountDaoImpl {
    2. // 需要注入的属性,类、普通值、复杂属性等等
    3. private ConnectionUtils connectionUtils;
    4. private String age;
    5. private String[] myArray;
    6. private Map<String, String> myMap;
    7. private Set<String> mySet;
    8. public void setConnectionUtils(ConnectionUtils connectionUtils) {
    9. this.connectionUtils = connectionUtils;
    10. }
    11. public void setAge(String age) {
    12. this.age = age;
    13. }
    14. public void setMyArray(String[] myArray) {
    15. this.myArray = myArray;
    16. }
    17. public void setMyMap(Map<String, String> myMap) {
    18. this.myMap = myMap;
    19. }
    20. public void setMySet(Set<String> mySet) {
    21. this.mySet = mySet;
    22. }
    23. }
    1. <!--通过property标签实现set方法注入,set+name之后锁定到传值的set⽅法了,通过反射技术可以调⽤该⽅法传⼊对应的值-->
    2. <bean id="accountDao"
    3. class="com.atm.dao.JdbcAccountDaoImpl">
    4. <!--set注入使用property标签,如果注入的是另外一个bean使用ref属性,如果注入的是普通纸使用value属性-->
    5. <property name="ConnectionUtils" ref="connectionUtils"/>
    6. <property name="age" value="18"/>
    7. <!--复杂属性的注入-->
    8. <property name="myArray">
    9. <array>
    10. <value>value1</value>
    11. <value>value2</value>
    12. </array>
    13. </property>
    14. <property name="myMap">
    15. <map>
    16. <entry key="key1" value="value1"/>
    17. <entry key="key2" value="value2"/>
    18. </map>
    19. </property>
    20. <property name="mySet">
    21. <set>
    22. <value>value1</value>
    23. <value>value2</value>
    24. </set>
    25. </property>
    26. </bean>
  10. 构造器注入

    1. public class JdbcAccountDaoImpl {
    2. // 需要注入的属性,类、普通值、复杂属性等等
    3. private ConnectionUtils connectionUtils;
    4. private String age;
    5. public JdbcAccountDaoImpl(ConnectionUtils connectionUtils,String age) {
    6. this.connectionUtils = connectionUtils;
    7. this.age = age;
    8. }
    9. }
    1. <bean id="accountDao" class="com.atm.dao.JdbcAccountDaoImpl">
    2. <!-- 构造器注入,name表示参数名称,index表示参数索引位置 -->
    3. <constructor-arg name="connectionUtils" ref="connectionUtils"/>
    4. <constructor-arg index="1" value="18"/>
    5. </bean>

    4.4 XML与注解结合模式

    4.4.1 注意事项

  11. 实际企业开发中,纯xml模式使⽤已经很少了

  12. 引⼊注解功能,不需要引⼊额外的jar
  13. xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
  14. 第三⽅jar中的bean定义在xml,⽐如driud数据库连接池;⾃⼰开发的bean定义使⽤注解

    4.4.2 xml标签与注解对应关系

  15. IOC相关 | xml形式 | 对应注解 | | —- | —- | | 标签 | @Component(“accountDao”),注解加在类上
    bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已 | | 标签的scope属性 | @Scope(“prototype”),默认单例,注解加在类上 | | 标签的init-method属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 | | 标签的destory-method属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |

  16. 依赖注入相关

  • @Autowired(推荐使⽤)
    • @Autowired采取的策略为按照类型注⼊。
  • @Qualifier
    • 使用@Autowierd时,当一个类型有多个Bean时,那么就会造成无法具体注入哪个bean的情况,这时就需要配合@Qualifier来使用,@Qualifier(name=”jdbcAccountDaoImpl”) 可以指定具体注入哪个对象
  • @Resource。

    • @Resource 默认按照 ByName ⾃动注⼊。@Resource(name=”manDao”,type=”ManDao”) 。
    • 如果同时指定name和type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
    • 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
    • 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
    • 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配。
    • @Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包
      1. <dependency>
      2. <groupId>javax.annotation</groupId>
      3. <artifactId>javax.annotation-api</artifactId>
      4. <version>1.3.2</version>
      5. </dependency>

      4.4.3 开启注解扫描

      ```xml <?xml version=”1.0” encoding=”UTF-8”?> <beans xmlns=”http://www.springframework.org/schema/beans“ xmlns:context=”http://www.springframework.org/schema/context“ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance“ xsi:schemaLocation=”
      1. http://www.springframework.org/schema/beans
      2. https://www.springframework.org/schema/beans/spring-beans.xsd
      3. http://www.springframework.org/schema/context
      4. https://www.springframework.org/schema/context/spring-context.xsd
      “>

  1. <a name="nTb0p"></a>
  2. # 4.5 纯注解模式
  3. 1. 在xml与注解结合中,第三⽅jar中的bean定义在xml,⽐如driud数据库连接池;⾃⼰开发的bean定义使⽤注解;所以全注解就是把定义在xml中的配置迁移到配置类中。
  4. 2. 代码
  5. ```java
  6. @Configuration
  7. @ComponentScan("com.atm") // 替代 context:component-scan
  8. @PropertySource({"classpath:jdbc.properties"}) // ⼊外部属性配置⽂件
  9. //@Import() 可以把其它启动类关联进来
  10. public class SpringConfig {
  11. @Value("${jdbc.driver}")
  12. private String driverClassName;
  13. @Value("${jdbc.url}")
  14. private String url;
  15. @Value("${jdbc.username}")
  16. private String username;
  17. @Value("${jdbc.password}")
  18. private String password;
  19. @Bean
  20. public DataSource dataSource() {
  21. DruidDataSource dataSource = new DruidDataSource();
  22. dataSource.setDriverClassName(driverClassName);
  23. dataSource.setUrl(url);
  24. dataSource.setUsername(username);
  25. dataSource.setPassword(password);
  26. return dataSource;
  27. }
  28. }
  1. 启动容器参考4.2

4.6 IOC的高级特性

4.6.1 lazy-init延迟加载

  1. Bean的延迟加载:ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。
  2. 如果不需要singleton Bean在容器初始化时就被立即实例化,可以把bean设置为延迟实例化。

    1. <!--lazy-init的值默认为false 如果是注解模式则用@Lazy-->
    2. <bean id="connectionUtils" class="com.atm.utils.ConnectionUtils" lazy-init="true"/>
  3. 设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。

  4. 如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
  5. 也可以在容器层次中通过在元素上使⽤ “default-lazy-init” 属性来控制延时初始化。如下⾯配置:

    1. <!--全局延迟加载-->
    2. <beans default-lazy-init="true">
    3. </beans>
  6. 如果⼀个 bean 的 scope 属性为 scope=”pototype” 时,即使设置了 lazy-init=”false”,容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。

  7. 应用场景:
  • 开启延迟加载⼀定程度提⾼容器启动和运转性能
  • 对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源

4.6.2 FactoryBean和BeanFactory

  1. 总结BeanFactory是IOC容器的顶层接口,定义了一些容器的基础行为,是负责创建和管理Bean的一个工厂。而FactoryBean本身就是一个Bean(工厂Bean),我们可以借助它来自定义其它复杂Bean的创建过程,定义完再放到容器中管理。
  2. Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。
  3. 代码示例
  • Company类

    1. @Data
    2. public class Company {
    3. private String name;
    4. private String address;
    5. private int scale;
    6. }
  • CompanyFactoryBean类

    1. public class CompanyFactoryBean implements FactoryBean<Company> {
    2. private String companyInfo; // 公司名称,地址,规模
    3. public void setCompanyInfo(String companyInfo) {
    4. this.companyInfo = companyInfo;
    5. }
    6. // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器
    7. // 的单例对象缓存池中Map
    8. @Override
    9. public Company getObject() throws Exception {
    10. // 模拟创建复杂对象Company
    11. Company company = new Company();
    12. String[] strings = companyInfo.split(",");
    13. company.setName(strings[0]);
    14. company.setAddress(strings[1]);
    15. company.setScale(Integer.parseInt(strings[2]));
    16. return company;
    17. }
    18. // 返回FactoryBean创建的Bean类型
    19. @Override
    20. public Class<?> getObjectType() {
    21. return Company.class;
    22. }
    23. // 返回作⽤域是否单例
    24. @Override
    25. public boolean isSingleton() {
    26. return true;
    27. }
    28. }
  • xml配置

    1. <bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
    2. <property name="companyInfo" value="atm,广州,500/>
    3. </bean>
  • 通过companyBean这个id获取的Bean对象是Company类,如果需要获取CompanyFactoryBean类则id改为&companyBean。

    4.6.3 后置处理器

  1. Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
  2. BeanPostProcessor 的使用
  • BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.

    1. /**
    2. * 定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理
    3. * 如果要对具体的某个bean处理,可以通过⽅法参数判断,
    4. * 注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
    5. */
    6. public class MyBeanPostProcessor implements BeanPostProcessor {
    7. // bean:第⼀个参数是每个bean的实例
    8. // beanName:第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判
    9. // 断我们将要处理的具体的bean。
    10. @Override
    11. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    12. return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    13. }
    14. @Override
    15. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    16. return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    17. }
    18. }
  1. BeanFactoryPostProcessor
  • BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer
  • 此接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法
  • 其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改。
  • BeanDefinition的⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。
  • BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition
  • 注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象

    4.6.4 Spring Bean的生命周期

    4. SpringIOC的应用 - 图3