Spring学习记录 (上篇)

一、Spring框架(高阶待更进)

1.1 Spring框架是什么

  • Spring是一种容器框架,用于配置各个组件(bean)并且维护各个组件bean之间的关系
  • Spring框架可以管理Web层,业务层,DAO层,持久层。
  • Spring提倡面向接口编程,配合DI技术可以实现层与层的解耦(主要是WEB层和业务层)
  • Spring框架图

1.2 一个简单的spring项目

  • 目录结构

    • 引入spring的开发包(最小配置spring.jar以及日志包common-logging.jar)
    • 创建spring的一个核心文件ApplicationContext.xml, 该文件一般放在src目录下,该文件中引入xsd文件,可以自定义文件名。
      TestSpring
          |---src
              |---com.netease
              |          |---Service
              |                |---HelloService.java
              |                |---ByeService.java
              |          |---Test
              |                |---TestService.java
              |---ApplicationContext.xml
      External Libraries
          |--- spring.jar
          |--- common-logging.jar
          |--- ...
      
  • 代码详情

    package com.netease.Service;
    public class HelloService {
        private String name;
        private ByeService byeService;      /** 引用了一个ByeService*/
    
        public String getName() { return name;}
        public void setName(String name) { this.name = name;}
    
        public ByeService getByeService() { return byeService;}
        public void setByeService(ByeService byeService) { this.byeService = byeService;}
    
        public void sayHello() {
            System.out.println("hello "+name);
        }
    }
    
    package com.netease.Service;
    public class ByeService {
        private String name;
    
        public String getName() { return name;}
        public void setName(String name) {this.name = name;}
    
        public void sayBye() {
            System.out.println("Bye " + name);
        }
    }
    
    package com.netease.Test;
    import com.netease.Service.HelloService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestService {
        public static void main(String[] args) {
            /*通过反射机制,在Spring容器中生成对象*/
            /*如果%%.xml放在某个包下,则就变为<包名+文件名>*/
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            HelloService helloService = (HelloService) applicationContext.getBean("helloService");
            helloService.sayHello();
            helloService.getByeService().sayBye();
        }
    }
    
  • Spring核心容器文件ApplicationContext.xml

    • 在容器文件中配置Bean(Bean有可能是以下各种类型service/dao/domain/action/数据源)
    • Spring框架加载时,会阅读该容器文件,自动创建一个bean对象,并放入内存
    • 学习框架,最重要的就是学习各种配置

      <?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"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
                    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
      
        <!--注意Spring如何配置Bean以及如何维护Bean之间的依赖-->
        <bean id="helloService" class="com.netease.Service.HelloService">
            <!--注入属性值-->
            <property name="name">
                <value>Today</value>
            </property>
            <!--维护Bean之间依赖,ref指向下一个Bean-->
            <property name="byeService" ref="byeService"/>
        </bean>
      
        <bean id="byeService" class="com.netease.Service.ByeService">
            <property name="name">
                <value>Yesterday</value>
            </property>
        </bean>
      </beans>
      

1.3 Spring框架运行原理图

  • Spring框架什么时候被加载,Spring中配置的bean怎样被创建,Bean与Bean之间的关系如何维护

1.4 IOC与DI是什么

  • IOC(Inverse Of Controll ) 控制反转: 所谓控制反转就是把创建对象(bean),和维护对象(bean)的关系的权利从程序中转移到spring的容器(applicationContext.xml),而程序本身不再维护.
  • DI(Dependency Injection) 依赖注入: 实际上DI和IOC是同一个概念,Spring设计者认为DI更准确表示Spring核心技术

二、装配Bean

2.1 容纳Bean

  • ApplicationContext方式
    • ApplicationContext ac = new ClassPathXmlApplicationContext("com/netease/bean.xml")
    • 这句话执行时,不仅实例化了该容器,其中配置的所有scope为singleton的bean全部通过反射机制被实例化,scope为prototype的bean不会被实例化
    • 其他三种加载方式
      1. ClassPathXmlApplicationContext:从类路径中加载。
      2. FileSystemXmlApplicationContext:从文件系统加载,需要全路径
      3. XmlWebApplicationContext:从web系统中加载。
    • 好处:预先加载,速度快;缺点:耗内存
    • Bean的作用域,即scope
  • Bean工厂方式
    • BeanFactory factory = new XmlBeanFactory(new ClassPathResource("com/netease/bean.xml"))
    • 这句话执行时,仅实例化了该容器,容器中的bean不被实例化,只有当你使用factory.getBean("***")获取某个bean时,才实例化该bean对应的对象。类似于延迟实例化
    • 好处:节约内存;缺点:速度慢
  • 一般没有特殊要求,都采用ApplicationContext方式实例化(90%),移动端可采用Bean工厂方式

2.2 Bean生命周期

  • 完整生命周期步骤如下(通常只用到加粗的几步):

    1. 实例化:程序加载ApplicationContext文件,并把bean(scope=singleton)实例化到内存
    2. 设置属性:调用set方法设置bean中指定的属性
    3. 如果你实现了BeanNameAware接口, 则可以通过BeanName
    4. 如果你实现了BeanFactoryAware接口,则可以获取BeanFactory
    5. 如果你实现了ApplicationContextAware接口,则可以获取ApplicationContext
    6. 如果bean和一个后置处理器关联,则会自动去调用 postProcessBeforeInitialization()方法
    7. 如果你实现InitializingBean接口,则会调用afterPropertiesSet()方法
    8. 如果设置了,则可以在bean文件中定义自己的初始化方法init.
    9. 如果bean和一个后置处理器关联,则会自动去调用 postProcessAfterInitialization()方法
    10. 使用bean
    11. 容器关闭
    12. 可以通过实现DisposableBean接口来调用方法 destory,用来关闭资源等
    13. 可以在 调用定制的销毁方法destroy
  • ApplicationContext方式

  • Bean工厂方式

2.3 装配Bean

2.3.1 什么是装配Bean

  • 告诉容器有哪些Bean以及容器如何使用依赖注入将它们配合在一起。

    2.3.2 装配方法

  • 在XML进行显式配置
  • 在Java中进行显示配置
  • 隐式的bean发现机制和自动装配

    2.3.3 面向XML的装配方法

    2.3.3.1 添加一个Bean

  • 基本配置:一个bean ID + 全称类名
    <beans>
       <bean id="foo" class="...Foo"/>
       <bean id="bar" class="...Bar"/>
    </beans>
    
  • scope属性

    <beans>
      <bean id="foo" scope="prototype/singleton" class="...Foo"/>
      <bean id="bar" class="...Bar"/>
    </beans>
    
    • 注意尽量使用默认的singleton,以防占用太大内存,影响程序性能
  • init-method 和destroy-method属性

    • 用途:在Spring实例化或者销毁bean时做一些处理工作
    • 使用方法:
      1. 声明方式:在XML文件中先声明该Bean拥有的初始化和销毁方法的函数名。在bean.java中实现initdestroy方法,函数名可自定义
         <bean id="foo" init-method="init" destory-method="destroy" class="...Foo" >
        
      2. 标签方式:不需要在xml文件中声明初始化和销毁方法,在bean.java中采用标签方式声明初始化和销毁方法
         @PostConstruct
         public void ini(){…}
         @PreDestroy
         public void destroy(){…}
        
    • 其他进行初始化和销毁处理的方法:
      • Spring还提供了两个接口供bean.java使用,来进行实例化或者销毁时的处理,但不推荐

        2.3.3.2 通过set方法注入属性和依赖

        通过元素的子元素注入属性和依赖
    • 注入简单属性(基本类型和String)
      <bean id="foo" class="...Foo">
        <property name="name">
              <value>tom</value>
        </property>
      </bean>
      
    • 注入依赖
  • 引用其他的Bean
    <bean id="foo" class="...Foo">
          <property name="name">
                <ref bean="bar">
          </property>
    </bean>
    <bean id="bar" class="...Bar">
    </bean>
    
  • 内部bean
    <bean id="foo" class="...Foo">
      <property name="bar">
            <bean class="...Bar">...</bean>
      </property>
    </bean>
    
  • 注入集合属性(数组,List,Set,Map)

  1. 设置null
     <property name="barlist">
            <null/>
     </property>
    
  2. 数组
    <!-- 给数组注入值 -->
    <property name="empName">
      <list>
        <value>小明</value>
        <value>李雷</value>
        <value>韩梅梅</value>
      </list>
    </property>
    
  3. List
      <!-- 给list注入值 list 中可以有相等的对象 -->
      <property name="empList">
           <list>
               <ref bean="emp2" />
               <ref bean="emp1"/>
               <ref bean="emp1"/>
           </list>
      </property>
    
  4. Set
      <!-- 给set注入值 set不能有相同的对象 -->
      <property name="empsets">
           <set>
               <ref bean="emp1" />
               <ref bean="emp2"/>
               <ref bean="emp2"/>
           </set>
      </property>
    
  5. Map
    <!-- 给map注入值只要key不同即可 -->
      <property name="empMaps">
           <map>
               <entry key="11" value-ref="emp1" />
               <entry key="22" value-ref="emp2"/>
               <entry key="33" value-ref="emp1"/>
           </map>
     </property>
    
  6. property
    <property name="pp">
       <props>
           <prop key="pp1">abcd</prop>
           <prop key="pp2">hello</prop>
       </props>
    </property>
    
  • Bean的继承

  1. public class Student有name和age两个属性
  2. public class Gradate extends Student 有degree属性
  3. 在ApplicationContext文件中体现配置
    • 即student中配置过的属性graduate可以不配置
    • 如果配置,则会覆盖父类的属性
       <!-- 配置一个学生对象 -->
       <bean id="student" class="com.hsp.inherit.Student">
           <property name="name" value="顺平" />
           <property name="age" value="30"/>
       </bean>
       <!-- 配置Grdate对象 -->
       <bean id="grdate" parent="student" class="com.hsp.inherit.Gradate">
           <!-- 如果自己配置属性name,age,则会替换从父对象继承的数据  -->
           <property name="name" value="小明"/>
           <property name="degree" value="学士"/>
       </bean>
      

      2.3.3.3 通过构造器注入属性

      通过\元素的\子元素注入属性
      <bean id="test" class ="...Test">
      <constructor-arg index="0" type="java.lang.String" value="朱洁" />
      <constructor-arg index="1" type="int" value="20"/>
      <constructor-arg  index="2" type="double" value="34.5" />
      </bean>
      

      2.3.3.4 分散配置

      通过在ApplicationContext.xml文件中配置context:property-placeholder 引入属性文件,有多个需要使用','号间隔.
    • 步骤如下:

  1. 配置属性文件db.property
     name=scott
     drivername=oracle:jdbc:driver:OracleDirver
     url=jdbc:oracle:thin:@127.0.0.1:1521:hsp
     pwd=tiger
    
  2. 在ApplicationContext.xml文件中引入db.properties文件,两种方式

    • 第一种

      <context:property-placeholder location = "classpath:com/netease/db.properties,classpath:com/netease/db2.properties,..."/>

    • 第二种
       <bean id="dbPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
           <property name="locations">
                <list>
                <value>xx/yy/db.properties</value>
                <value>xx/yy/db2.properties</value>
                </list>
             </property>
       </bean>
      
  3. 在ApplicationContext.xml文件中配置bean,采用占位符$方式
     <bean id="dbutil" class="com.hsp.dispatch.DBUtil">
     <property name="name" value="${name}" />
     <property name="drivername" value="${drivername}" />
     <property name="url" value="${url}" />
     <property name="pwd" value="${pwd}" />
     </bean>
    

    2.3.3.5 自动装配

    • 通过autowire属性装配
    • <bean id="foo" autowire="autowire type">
    • 有四种自动装配类型:
    1. byName寻找和属性名相同的bean,若找不到,则装不上。(属性名和bean id名必须相同)
    2. byType:寻找和属性类型相同的bean,找不到,装不上,找到多个抛异常。
    3. constructor:查找和bean的构造参数一致的一个或多个bean,若找不到或找到多个,抛异常。按照参数的类型装配
    4. autodetect: (3)和(2)之间选一个方式。不确定性的处理与(3)和(2)一致。
    5. defualt : 这个需要在
    6. no : 不自动装配,这是autowrite的默认值。
    • default说明

  • 需要在
    • 如果在指定了default-atuowrite后,所有的bean的默认的autowire就是中指定的装配方法;
    • 如果没有在指定defualt-autorwire,则默认是defualt-autorwire=”no”,所有的bean则默认不自动装配,除非自己配置autowire

2.3.3.6 引入注解简化配置

  • 如何启用组件扫描
    1. 注解方式:在某类上打上@ComponentScan标签,则可以扫描该类所在包及其子包下面的所有带Component注解的类,即所有组件;
    2. XML配置方式:context:component-scan base-package="package-name"
  • 自动装配方法

    • 需要激活相应的 Bean 后处理器,在XML中增加<context:annotation-config />方可使用;
    • 使用 @Repository、@Service、@Controller 和 @Component 将类标识为 Bean
      • 用途:声明一个Bean;不同层次,相同功能:
        • @Component 是一个泛化的概念,仅仅表示一个组件 (Bean),可以作用在任何层次。
        • @Repository 用于将数据访问层(DAO层)的类标识为Bean;
        • @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
        • @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。
      • 注解标注范围:类级别。因为该注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为Spring的数据访问异常类型。
      • Bean的名称:对应于XML配置方式中的id属性;如果注解不包含name属性,则bean的默认id为小写开头的非限定类名
      • Bean的作用域:对应于XML配置方式中的scope属性;默认属性均为singleton,提供@Scope注解对作用域进行修改
        @Scope("prototype")
        @Repository
        public class Demo { … }
        
    • 使用 @PostConstruct 和 @PreDestroy 指定生命周期回调方法

      • 用途:通常是针对一些资源的获取和释放操作
      • 注解标注范围:方法级别。标注于初始化之后执行的回调方法或者销毁之前执行的回调方法上;
      • 与XML的对应关系:对应于XML中的 init-method 和 destroy-method 属性;
    • 使用 @Required 进行 Bean 的依赖检查

      • 用途:判断给定 Bean 的相应 Setter 方法是否都在实例化的时候被调用了,而不是判断字段是否已经存在值了
        • Spring 进行依赖检查时,只会判断属性是否使用了 Setter 注入。如果某个属性没有使用 Setter 注入,即使是通过构造函数已经为该属性注入了值,Spring 仍然认为它没有执行注入,从而抛出异常。
        • Spring 只管是否通过 Setter 执行了注入,而对注入的值却没有任何要求,即使注入的 ,Spring 也认为是执行了依赖注入。
      • 注解标注范围:Setter方法。为依赖注入的本质是检查 Setter 方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非 setXxxx() 类型的方法则被忽略。
      • 与XML的对应关系:对应于XML配置方式中的dependency-check属性

        属性 | 含义 --------|------------------ none | 默认不执行依赖检查 simple | 对原始基本类型和集合类型进行检查 objects | 对复杂类型进行检查 all | 对所有类型进行检查

使用 @Resource、@Autowired 和 @Qualifier 指定 Bean 的自动装配策略

  • 用途:用于依赖的注入,声明和其他bean的关系
  • @Autowired
    • 匹配方式:byType
    • 注解标注范围:用于Setter方法、构造函数、字段,甚至普通方法,前提是方法必须有至少一个参数。
    • 作用于数组和使用泛型的集合类型时:Spring 会将容器中所有类型符合的 Bean 注入进来。
    • 作用于普通方法时,会产生一个副作用,就是在容器初始化该 Bean 实例的时候就会调用该方法。
  • @Qualifier
    • 当容器中存在多个 Bean 的类型与需要注入的相同时,注入将不能执行,可以给 @Autowired 增加一个候选值,做法是在 @Autowired 后面增加一个 @Qualifier 标注,提供一个 String 类型的值作为候选的 Bean 的名字。
      @Autowired(required=false)
      @Qualifier("ppp")
      public void setPerson(person p){}
      
    • 可以作用于方法的参数 ( 对于方法只有一个参数的情况,我们可以将 @Qualifer 标注放置在方法声明上面,但是推荐放置在参数前面 ),举例如下:
      @Autowired(required=false)
      public void sayHello(@Qualifier("ppp")Person p,String name){}
      
    • 如果没有明确指定 Bean 的 qualifier 名字,那么默认名字就是 Bean 的名字。当然也可以在配置文件中指定某个 Bean 的 qualifier 名字,方法如下:
      <bean id="person" class="footmark.spring.Person">
         <qualifier value="ppp"/>
      </bean>
      
  • @Resource
    • 匹配方式:byName->byTpye
    • 注解标注范围:作用于带一个参数的 Setter 方法、字段,以及带一个参数的普通方法上
    • @Resource 注解有一个 name 属性,用于指定 Bean 在配置文件中对应的名字。如果没有指定 name 属性,那么默认值就是字段或者属性的名字。
  • 与XML的对应关系:对应于XML配置中的autowire属性
    • \ 的 primary 和 autowire-candidate 属性对 @Resource、@Autowired 仍然有效。

2.3.4 面向Java文件的装配方法

  • 使用java文件来配置spring
    • Spring 3.0 新增了另外两个实现类:AnnotationConfigApplicationContext 和 AnnotationConfigWebApplicationContext。从名字便可以看出,它们是为注解而生,直接依赖于注解作为容器配置信息来源的 IoC 容器初始化类。AnnotationConfigApplicationContext 搭配上 @Configuration 和 @Bean 注解,自此,XML 配置方式不再是 Spring IoC 容器的唯一配置方式。
    • 之前,我们将配置信息集中写在 XML 中,如今使用注解,配置信息的载体由 XML 文件转移到了 Java 类中。我们通常将用于存放配置信息的类的类名以 “Config” 结尾,比如 AppDaoConfig.java、AppServiceConfig.java 等等。我们需要在用于指定配置信息的类上加上 @Configuration 注解,以明确指出该类是 Bean 配置的信息源。
  • 示例如下
    @Configuration
    public class BookStoreDaoConfig{
       @Bean
       public UserDao userDao(){ return new UserDaoImpl();}
       @Bean
       public BookDao bookDao(){return new BookDaoImpl();}
    }
    
    Spring 在解析到以上文件时,将识别出标注 @Bean 的所有方法,执行之,并将方法的返回值 ( 这里是 UserDaoImpl 和 BookDaoImpl 对象 ) 注册到 IoC 容器中。默认情况下,Bean 的名字即为方法名。因此,与以上配置等价的 XML 配置如下:
    <bean id=”userDao” class=”bookstore.dao.UserDaoImpl”/>
    <bean id=”bookDao” class=”bookstore.dao.BookDaoImpl”/>
    
  • 基于注解的容器初始化
    • AnnotationConfigApplicationContext():该构造函数初始化一个空容器,容器不包含任何 Bean 信息,需要在稍后通过调用其 register() 方法注册配置类,并调用 refresh() 方法刷新容器。
    • AnnotationConfigApplicationContext(Class<?>... annotatedClasses):这是最常用的构造函数,通过将涉及到的配置类传递给该构造函数,以实现将相应配置类中的 Bean 自动注册到容器中。
    • AnnotationConfigApplicationContext(String... basePackages):该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的 Spring Bean,将其注册到容器中。它不但识别标注 @Configuration 的配置类并正确解析,而且同样能识别使用 @Repository、@Service、@Controller、@Component 标注的类。

2.3.5 混合配置

  • 以XML为中心
    • 对于已经存在的大型项目,可能初期是以 XML 进行 Bean 配置的,后续逐渐加入了注解的支持.
      • 方法一:我们只需在 XML 配置文件中将被 @Configuration 标注的类定义为普通的 ,同时注册处理注解的 Bean 后处理器即可。如果存在多个标注了 @Configuration 的类,则需要在 XML 文件中逐一列出。示例如下:
        // 假设存在如下的 @Configuration 类:
         package bookstore.config;
         import bookstore.dao.*;
         @Configuration
         public class MyConfig{
         @Bean
            public UserDao userDao(){
                return new UserDaoImpl();
            }
         }
        //此时,只需在 XML 中作如下声明即可:
         <beans … >
            ……
            <context:annotation-config />
            <bean class=”demo.config.MyConfig”/>
         </beans>
        
      • 方法二:自动扫描功能。由于 @Configuration 注解本身也用 @Component 标注了,Spring 将能够识别出 @Configuration 标注类并正确解析之。
        <context:component-scan base-package=”bookstore.config” />
        
  • 以java文件为中心
    • 只需使用 @ImportResource 注解引入存在的 XML 即可,如下所示:
       @Configuration
       @ImportResource(“classpath:/bookstore/config/spring-beans.xml”)
       public class MyConfig{
       }
       // 容器的初始化过程和纯粹的以配置为中心的方式一致:
       AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
      

相关阅读:Spring学习记录 (下篇)

本文来自网易实践者社区,经作者朱洁授权发布。