佟刚Spring4.0学习笔记

 

1. 概述

Spring是一个Java领域的开源框架,它为简化企业级应用开发而生,可以使用简单的JavaBean实现以前只有EJB才能实现的功能。

1.1. 特性

  1. 轻量级(非侵入性):基于Spring开发的时候,我们不需要实现Spring提供的任何接口,也不需要继承Spring提供的任何类,然后我们就可以使用Spring给我们提供的功能
  2. 一站式框架:在IOC的基础上可以整合各种企业级开源框架和优秀的第三方类库
  3. 组件组合:Spring实现了使用简单的组件组合成一个复杂的应用,可以使用XML和Java注解组合这些组件

1.2. 模块

1.3. 配置文件

一个典型的Spring项目需要创建一个或多个Bean配置文件,Bean配置文件可以放在类路径下,也可以放在其它目录下。

2. Hello World

HelloWorld.java

package org.lin.stu.spring;

public class HelloWorld {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void say() {
        System.out.println("Hello " + name);
    }
}

2.1. 传统方式

//创建HelloWorld的一个对象
HelloWorld helloWorld = new HelloWorld();
//为name属性赋值
helloWorld.setName("World");
//调用方法
helloWorld.say();//Hello World

2.2. 使用Spring

<?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">
    
    <!--初始化IOC容器时,Spring会调用HelloWorld.java的无参构造创建名为helloWorld的对象,然后调用setName(String name)方法为name属性赋值-->
    <bean id="helloWorld" class="org.lin.stu.spring.HelloWorld">
        <!--name属性值为setter风格的属性名-->
        <property name="name" value="World"/>
    </bean>
</beans>
//创建Spring的IOC容器,ClassPathXmlApplicationContext表示配置以XML文件的形式存放在类路径下
//创建IOC容器后会立即初始化(即创建并初始化配置的Bean)
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//从IOC容器中获取Bean
HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");
//调用方法
helloWorld.say();//Hello World

3. Bean配置

3.1. 在XML文件中通过bean节点来配置Bean

<!--class属性值为全类名,即Spring通过反射的方式创建Bean-->
<bean id="helloWorld" class="org.lin.stu.spring.HelloWorld">
    <property name="name" value="World"/>
</bean>

id(Bean的名称):

  • 在IOC容器中必须是唯一的
  • 若id没有指定,Spring将自动生成,格式为:全类名#数字(例如:org.lin.stu.spring.HelloWorld#0)

3.2. BeanFactory&ApplicationContext

BeanFactory是IOC容器的基本实现,ApplicationContext提供了更多的高级特性,是BeanFactory的子接口。

BeanFactory是Spring框架的基础设施,面向Spring本身,而ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory。

3.3. ApplicationContext

  • ApplicationContext的主要实现类:
    • ClassPathXmlApplicationContext:从类路径下加载配置文件
    • FileSystemXmlApplicationContext:从文件系统中加载配置文件
  • IOC容器初始化时就创建并初始化所有的单例Bean
  • WebApplicationContext是专门为WEB应用而准备的

3.4. 通过类型获取Bean

HelloWorld helloWorld = ctx.getBean(HelloWorld.class);

如果IOC容器中有多个同类型的Bean,则抛出NoUniqueBeanDefinitionException异常。

3.5. 依赖注入

Spring支持3种依赖注入方式:

  • 属性注入
  • 构造器注入
  • 工厂方法注入(很少使用,不推荐)

3.5.1. 属性注入

  • 属性注入即通过set方法注入Bean的属性值或依赖的对象
  • 属性注入使用<property>元素,name属性值为setter风格的属性名,value属性或<value>子节点指定属性值
  • 属性注入是实际应用中最常用的注入方式

3.5.2. 构造器注入

通过构造器注入Bean的属性值或依赖的对象

Car.java

package org.lin.stu.spring;

public class Car {

    private String brand;//品牌
    private Double price;
    private Integer maxSpeed;

    public Car() {
    }

    public Car(String brand, Double price) {
        this.brand = brand;
        this.price = price;
    }

    public Car(String brand, Integer maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }

    //getters and setters are omitted
}
<bean id="car1" class="org.lin.stu.spring.Car">
    <constructor-arg value="Audi"/>
    <constructor-arg value="300000"/>
</bean>

<bean id="car2" class="org.lin.stu.spring.Car">
    <constructor-arg value="Audi"/>
    <constructor-arg value="240"/>
</bean>

以上代码等价于:

<bean id="car1" class="org.lin.stu.spring.Car">
    <constructor-arg value="Audi" index="0"/>
    <constructor-arg value="300000" index="1"/>
</bean>

<bean id="car2" class="org.lin.stu.spring.Car">
    <constructor-arg value="Audi" index="0"/>
    <constructor-arg value="240" index="1"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Car car1 = (Car) ctx.getBean("car1");
Car car2 = (Car) ctx.getBean("car2");
System.out.println(car1);//Car{brand='Audi', price=null, maxSpeed=300000}
System.out.println(car2);//Car{brand='Audi', price=null, maxSpeed=240}

因为300000和240既可以转成Double也可以转成Integer,此时仅靠顺序无法实现目的。

可以通过type属性避免上述问题:

<bean id="car1" class="org.lin.stu.spring.Car">
    <constructor-arg value="Audi"/>
    <constructor-arg value="300000" type="java.lang.Double"/>
</bean>

<bean id="car2" class="org.lin.stu.spring.Car">
    <constructor-arg value="Audi" index="0"/>
    <constructor-arg type="java.lang.Integer">
        <value>240</value>
    </constructor-arg >
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Car car1 = (Car) ctx.getBean("car1");
Car car2 = (Car) ctx.getBean("car2");
System.out.println(car1);//Car{brand='Audi', price=300000.0, maxSpeed=null}
System.out.println(car2);//Car{brand='Audi', price=null, maxSpeed=240}

3.6. 字面值

  • 可用字符串表示的值(基本数据类型及其封装类型、String等类型),可以通过<value>标签或value属性进行注入
  • 若字面值中包含特殊字符(例如‘<’等字符),可以使用<![CDATA[]]>把字面值包裹起来,但只能通过<value>标签方式注入,例如:<value><![CDATA[<Audi>]]></value>

3.7. 引用其它Bean

  • 组成应用程序的Bean经常需要相互协作以完成应用程序的功能
  • 在Bean的配置文件中,可以通过<ref>标签或ref属性为Bean的属性指定引用的Bean
  • 也可以在<property>标签或<constructor-arg>标签里包含Bean的声明,这样的Bean称为内部Bean
package org.lin.stu.spring;

public class Person {
    private String name;
    private Car car;

    //getters and setters are omitted
}
package org.lin.stu.spring;

public class Car {
    private String brand;
    private Double price;
    private Integer maxSpeed;

    //getters and setters are omitted
}
<bean id="car" class="org.lin.stu.spring.Car">
    <property name="brand" value="Audi"/>
    <property name="price" value="300000"/>
</bean>

<bean id="person" class="org.lin.stu.spring.Person">
    <property name="name" value="Alan"/>
    <property name="car">
        <ref bean="car"/>
    </property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Alan', car=Car{brand='Audi', price=300000.0, maxSpeed=null}}

3.8. 内部Bean

  • 当某个Bean仅在某个地方使用时,可以将其声明为内部Bean,内部Bean声明直接包含在<property>或<constructor-arg>标签里,不需要设置id属性
  • 内部Bean不能使用在任何其他地方
package org.lin.stu.spring;

public class Person {
    private String name;
    private Car car;

    //getters and setters are omitted
}
package org.lin.stu.spring;

public class Car {
    private String brand;
    private Double price;
    private Integer maxSpeed;

    //getters and setters are omitted
}
<bean id="person" class="org.lin.stu.spring.Person">
    <property name="name" value="Alan"/>
    <property name="car">
        <bean class="org.lin.stu.spring.Car">
            <property name="brand" value="Audi"/>
            <property name="price" value="300000"/>
        </bean>
    </property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Alan', car=Car{brand='Audi', price=300000.0, maxSpeed=null}}

3.9. 空值和级联属性

  • 可以使用<null/>标签为Bean的属性注入null值
  • Spring支持级联属性的配置
<bean id="person" class="org.lin.stu.spring.Person">
    <property name="name" value="Alan"/>
    <!--级联属性赋值前需要先指定引用-->
    <property name="car" ref="car"/>
    <property name="car.brand" value="Ford"/>
</bean>

3.10. 集合属性

  • 可以使用一组内置的XML标签配置集合属性
  • java.util.List类型的属性需要使用<list>标签配置,在标签里配置一些元素,这些元素可以是<value>元素(配置字面值),也可以是<ref>元素(指定对其他Bean的引用),还可以是<bean>元素(配置内置Bean),还可以是<null/>元素,甚至可以内嵌其他集合
  • 数组和List一样,也使用<list>标签
  • java.util.Set类型的属性需要使用<set>标签配置,配置方法与List一样
  • java.util.Map类型的属性需要使用<map>标签配置,<map>标签里可以使用多个<entry>标签,每个<entry>标签包含一个键和一个值,因为键和值的类型没有限制,所以可以自由地使用<value>,<ref>,<bean>或<null/>标签为它们指定值,可以通过属性指定键和值,简单值通过key和value属性指定,Bean引用通过key-ref和value-ref属性指定
  • java.util.Properties类型的属性需要使用<props>标签配置,<props>标签里可以使用多个<prop>标签,每个<prop>标签必须指定key属性

3.10.1. List

package org.lin.stu.spring;

public class Car {
    private String brand;
    private Double price;
    private Integer maxSpeed;

    //getters and setters are omitted
}
package org.lin.stu.spring;

import java.util.List;

public class Person {
    private String name;
    private List<Car> cars;

    //getters and setters are omitted
}
<bean id="car1" class="org.lin.stu.spring.Car">
    <property name="brand" value="Audi"/>
    <property name="price" value="300000"/>
</bean>

<bean id="person" class="org.lin.stu.spring.Person">
    <property name="name" value="Mike"/>
    <property name="cars">
        <list>
            <ref bean="car1"/>
        </list>
    </property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Mike', cars=[Car{brand='Audi', price=300000.0, maxSpeed=null}]}

3.10.2. Map

package org.lin.stu.spring;

public class Car {
    private String brand;
    private Double price;
    private Integer maxSpeed;

    //getters and setters are omitted
}
package org.lin.stu.spring;

import java.util.Map;

public class Person {
    private String name;
    private Map<String, Car> cars;

    //getters and setters are omitted
}
<bean id="car1" class="org.lin.stu.spring.Car">
    <property name="brand" value="Audi"/>
    <property name="price" value="300000"/>
</bean>

<bean id="person" class="org.lin.stu.spring.Person">
    <property name="name" value="Mike"/>
    <property name="cars">
        <map>
            <entry key="A" value-ref="car1"/>
        </map>
    </property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Mike', cars={A=Car{brand='Audi', price=300000.0, maxSpeed=null}}}

3.10.3. Properties

package org.lin.stu.spring;

import java.util.Properties;

public class DataSource {
    private Properties properties;

    //getters and setters are omitted
}
<bean id="dataSource" class="org.lin.stu.spring.DataSource">
    <property name="properties">
        <props>
            <prop key="username">root</prop>
            <prop key="password">123</prop>
            <prop key="url">jdbc:mysql///test</prop>
            <prop key="driverClass">com.mysql.jdbc.Driver</prop>
        </props>
    </property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
//DataSource{properties={password=123, driverClass=com.mysql.jdbc.Driver, url=jdbc:mysql///test, username=root}}

3.11. 使用util scheme配置集合

  • 基本的集合配置不能将其作为独立的定义,导致其他Bean无法引用,无法在不同Bean之间共享
  • 可以使用util schema配置独立的集合
package org.lin.stu.spring;

public class Car {
    private String brand;
    private Double price;
    private Integer maxSpeed;

    //getters and setters are omitted
}
<bean id="car" class="org.lin.stu.spring.Car">
    <property name="brand" value="Audi"/>
    <property name="price" value="300000"/>
</bean>

<bean id="person" class="org.lin.stu.spring.Person">
    <property name="name" value="Mike"/>
    <property name="cars" ref="cars"/>
</bean>

<util:list id="cars">
    <ref bean="car"/>
</util:list>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Mike', cars=[Car{brand='Audi', price=300000.0, maxSpeed=null}]}

3.12. 使用p命名空间

为了简化配置,越来越多的XML文件采用属性而非子元素。Spring从2.5开始引入了一个新的p命名空间,可以通过属性的方式配置Bean的属性,使用p命名空间后,基于XML的配置将进一步简化。

3.13. 自动装配

Spring IOC容器可以自动装配Bean,需要做的仅仅是通过<bean>的autowire属性指定自动装配的模式。

  • byType(根据类型自动装配):若IOC容器中有多个类型匹配的Bean,则抛出NoUniqueBeanDefinitionException异常
  • byName(根据名称自动装配):必须将目标Bean的名称和setter风格的属性名设成一样
  • constructor(根据构造器自动装配):当类中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用
package org.lin.stu.spring.autowire;

public class Person {
    private String name;
    private Address address;
    private Car car;

    //getters and setters are omitted
}
package org.lin.stu.spring.autowire;

public class Address {
    private String city;
    private String street;

    //getters and setters are omitted
}
package org.lin.stu.spring.autowire;

public class Car {
    private String brand;
    private Double price;

    //getters and setters are omitted
}

3.13.1. byName

<bean id="addr" class="org.lin.stu.spring.autowire.Address" p:city="ChengDu" p:street="ShengBangJie"/>
<bean id="car" class="org.lin.stu.spring.autowire.Car" p:brand="Audi" p:price="300000"/>
<bean id="person" class="org.lin.stu.spring.autowire.Person" p:address-ref="addr" p:name="Tom" autowire="byName"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("autowire.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Tom', address=Address{city='ChengDu', street='ShengBangJie'}, car=Car{brand='Audi', price=300000.0}}

3.13.2. byType

<bean id="addr" class="org.lin.stu.spring.autowire.Address" p:city="ChengDu" p:street="ShengBangJie"/>
<bean id="car" class="org.lin.stu.spring.autowire.Car" p:brand="Audi" p:price="300000"/>
<bean id="person" class="org.lin.stu.spring.autowire.Person" p:name="Tom" autowire="byType"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("autowire.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Tom', address=Address{city='ChengDu', street='ShengBangJie'}, car=Car{brand='Audi', price=300000.0}}

3.13.3. 自动装配的缺点

  • 在Bean配置里设置autowire属性进行自动装配将会装配Bean的所有属性
  • 要么根据类型自动装配,要么根据名称自动装配,不能两者兼有
  • 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力

3.14. Bean之间的继承关系

  • Spring允许Bean之间进行继承,被继承的Bean称为父Bean,继承的Bean称为子Bean
  • 子Bean继承父Bean的配置,包括属性配置
  • 子Bean也可以覆盖从父Bean继承过来的配置
  • 父Bean可以作为配置模板,也可以作为Bean实例,若只想把父Bean作为配置模板,可以设置abstract属性为true,这样Spring将不会实例化这个Bean
  • 并不是<bean>元素的所有属性都会被继承,比如:autowire,abstract等
  • 也可以忽略父Bean的class属性,让子Bean自己指定,而共享相同的属性配置,但此时该Bean必须是抽象Bean
package org.lin.stu.spring.relation;

public class Address {
    private String city;
    private String street;

    //getters and setters are omitted
}
<bean id="address1" class="org.lin.stu.spring.relation.Address" p:city="ChengDu" p:street="YuLinLu"/>
<bean id="address2" p:street="BaiCaoLu" parent="address1"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("relation.xml");
Address address1 = (Address) ctx.getBean("address1");
Address address2 = (Address) ctx.getBean("address2");
System.out.println(address1);//Address{city='ChengDu', street='YuLinLu'}
System.out.println(address2);//Address{city='ChengDu', street='BaiCaoLu'}

3.15. Bean之间的依赖关系

Spring允许通过depends-on属性设置依赖的Bean,依赖的Bean会在本Bean实例化之前装配好,如果容器中没有依赖的Bean,则会抛出NoSuchBeanDefinitionException异常。

3.16. Bean的作用域

在Spring中,可以通过<bean>标签的scope属性设置Bean的作用域,取值如下:

  • singleton:默认值,容器初始化时创建该Bean,在整个容器的生命周期内只创建这一次
  • prototype:容器初始化时不创建,每次调用getBean(...)都会创建并返回一个新实例
  • request:每次HTTP请求都会创建一个新实例,该作用域仅适用于WebApplicationContext环境
  • session:同一个HttpSession共享一个实例,该作用域仅适用于WebApplicationContext环境
package org.lin.stu.spring.scope;

public class Car {
    private String brand;
    private Double price;

    //getters and setters are omitted
}
<bean id="car" class="org.lin.stu.spring.scope.Car" p:brand="Audi" p:price="300000"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("scope.xml");
Car car1 = (Car) ctx.getBean("car");
Car car2 = (Car) ctx.getBean("car");
System.out.println(car1 == car2);//true

3.17. 使用外部属性文件

  • Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,它允许用户将Bean配置的部分内容(例如:文件上传目录、数据源信息等)外移到属性文件中,在Bean配置文件里使用${propName}代替
  • Spring还允许在外部属性文件中使用${propName},以实现属性之间的相互引用

3.17.1. Spring2.5之前

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:db.properties"/>
</bean>

3.17.2. Spring2.5之后

<context:property-placeholder location="classpath:db.properties"/>

示例:

package org.lin.stu.spring.properties;

public class DataSource {
    private String user;
    private String password;
    private String driverClass;
    private String jdbcUrl;

    //getters and setters are omitted
}

db.properties

user=root
password=${user}
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="org.lin.stu.spring.properties.DataSource">
    <property name="user" value="${user}"/>
    <property name="password" value="${password}"/>
    <property name="driverClass" value="${driverClass}"/>
    <property name="jdbcUrl" value="${jdbcUrl}"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("properties.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);//DataSource{user='root', password='root', driverClass='com.mysql.jdbc.Driver', jdbcUrl='jdbc:mysql:///test'}

3.18. SpEL(Spring表达式语言)

SpEL使用#{}作为定界符,在大括号中的内容被认为是SpEL,SpEL为bean的属性进行动态赋值提供了便利。

3.18.1. 字面值

<!--整数-->
<property name="age" value="#{25}"/>
<!--小数-->
<property name="weight" value="#{120.5}"/>
<!--科学计数法-->
<property name="height" value="#{1e2}"/>
<!--String可以使用单引号或者双引号作为定界符号-->
<property name="truename" value="#{'唐僧'}"/>
<property name='nickname' value='#{"小唐"}'/>
<!--布尔值-->
<property name="isMale" value="#{false}"/>

3.18.2. 引用其他Bean

<property name="car" value="#{car}"/>

3.18.3. 引用其他Bean的属性

<property name="city" value="#{address.city}"/>

3.18.4. 调用其他Bean的方法(支持链式调用)

<property name="info" value="#{user.toString()}"/>
<property name="upperCaseInfo" value="#{user.toString().toUpperCase()}"/>

//TODO

3.18.?. 示例

package org.lin.stu.spring.spel;

public class Address {
    private String city;
    private String street;

    //getters and setters are omitted
}
package org.lin.stu.spring.spel;

public class Car {
    private String brand;
    private Double price;
    private Double tyrePerimeter;

    //getters and setters are omitted
}
package org.lin.stu.spring.spel;

public class Person {
    private String name;
    private Car car;
    private String city;
    //根据car的price确定,如果price>=300000,info为金领,否则为白领
    private String info;

    //getters and setters are omitted
}
<bean id="address" class="org.lin.stu.spring.spel.Address">
    <!--使用SpEL为属性赋字面值-->
    <property name="city" value="#{'ChengDu'}"/>
    <property name="street" value="BaiCaoLu"/>
</bean>

<bean id="car" class="org.lin.stu.spring.spel.Car">
    <property name="brand" value="Audi"/>
    <property name="price" value="300000"/>
    <!--使用SpEL访问类的静态属性(也可以调用类的静态方法)-->
    <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"/>
</bean>

<bean id="person" class="org.lin.stu.spring.spel.Person">
    <property name="name" value="Tom"/>
    <!--使用SpEL引用其他的Bean-->
    <property name="car" value="#{car}"/>
    <!--使用SpEL引用其他Bean的属性-->
    <property name="city" value="#{address.city}"/>
    <property name="info" value="#{car.price >= 300000 ? '金领' : '白领'}"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("spel.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Tom', car=Car{brand='Audi', price=300000.0, tyrePerimeter=251.32741228718345}, city='ChengDu', info='金领'}

猜你喜欢

转载自my.oschina.net/u/2539475/blog/1621418
今日推荐