Spring框架框架的核心功能有两个:
- Spring容器作为超级大工厂,负责创建、管理所有的java对象,这些java对象被称作Bean。
- Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称作“依赖注入”的方式来管理Bean之间的依赖关系。
1 IOC和DI
IOC(Inversion of Control):其思想是反转资源获取的方向。传统的资源查找方式要求组件向容器发起请求查找资源,作为回应,容器实时的返回资源。而应用了IOC之后,则是容器主动的将资源推送给它所管理的组件,组件要做的则是选择一种合适的方式来接受资源。这种行为也成为被查找的被动形式。
DI(Depedency Injection):IOC的另一种表达方式,即组件以一些预先定义好的方式(例如:setter方法)接受来自容器的资源注入。相对于IOC而言,这种表述更直接。
2 IOC的发展历程
举一个例子,需求是需要生成一个HTML和PDF的不同类型的报表。以前我们一般会这样做,如UML类图1所示:
图1
service层依赖ReportGenrator接口,pdf和html的Generator去实现ReportGenrator接口,然后ReportService去关联PdfReportGenerator类和HtmlReportGenerator类。这样子Service层与其报表生成的两个类的耦合度就非常高。发展到后来开发者们开始利用工厂设计模式,service层不再去和PdfReportService和HtmlReportGenerator类去关联,而是去关联ReportGeneratorFactory工厂,由工厂去生产这两个类。当service层需要时向工厂提出需求,有工厂负责生产。如图2所示:
图2
发展到今天,Spring采用了IOC即反转控制。此时service层不再需求去主动请求PdfReportGenerator和HtmlReportGenerator的资源,而是有容器Container主动去注入资源,也就是service层被动接受来自Container的资源。service层和PdfReportGenerator、HtmlReportGenerator类无耦合,整个设计的耦合度大大降低。如图3所示:
图3
3 在Spring的IOC容器中配置bean
这里以HelloWorld为例,首先给出所有代码,然后再去做一个小结。
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">package com.alibaba.beans;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
//创建spring的IOC容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//从IOC容器中获取bean实例
HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");
helloWorld.hello();
}
}</span></span></span>
Bean代码:
<span style="font-size:18px;"><span style="font-size:18px;">package com.alibaba.beans;
public class HelloWorld
{
private String name;
public void setName(String name)
{
System.out.println("setName: " + name);
this.name = name;
}
public void hello()
{
System.out.println("hello:" + name);
}
public HelloWorld()
{
System.out.println("HelloWorld's constructor ...");
}
}
</span></span>
applicationContext.xml代码:
<span style="font-size:18px;"><span style="font-size:18px;"><?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">
<!-- 配置bean -->
<bean id="helloWorld" class="com.alibaba.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
</beans>
</span></span>
id作为Bean的名称,在IOC容器中必须是唯一的。若id没有指定,则Spring会将类的全限定名作为bean的名字。这里的class即就是bean的全类名,spring会通过反射在IOC容器中创建Bean,所以要求Bean中必须有无参数的构造器。在Main类中的ApplicationContext就是IOC容器,在SpringIOC容器读取Bean配置创建Bean实例之前,必须对它进行实例化才可以从IOC容器中获取bean实例。
4 Spring容器
Spring提供了两种类型的IOC容器实现:
- BeanFactory:IOC容器的基本实现。
- ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口,它增强了BeanFactory的功能。
大部分时候,都不会使用BeanFactory实例作为Spring容器,而是使用ApplicationContext实例作为容器。ApplicationContext的主要实现类有两个:
- ClassPathXmlApplicationContex:从类路径下加载配置文件。
-
FileSystemXmlApplicationContext:从文件系统中加载配置文件。
ConfigurableApplicationContext扩展于ApplicationContext,新增加两个主要方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力。ApplicationContext在初始化上下文时就实例化所有单例的Bean。接下来列出其类的关系图及UML类图,分别如图4、图5所示。
图4
图5
5 依赖注入的方式
Spring支持的依赖注入主要有两种:属性注入(摄值注入)和构造器注入。属性注入即通过setter方法注入Bean的属性值或依赖的对象。属性注入使用<property>元素,使用name属性指定Bean的属性名称,value属性或<value>子节点指定属性值。属性注入是实际应用中最常用的注入方式。
6 引用其他Bean
在这里我们添加一个Car类和Person类,让Person类去引用Car类。附上Car和Person类的代码。
package com.alibaba.beans;
public class Person
{
private String name;
private int age;
private Car car;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Car getCar()
{
return car;
}
public void setCar(Car car)
{
this.car = car;
}
@Override
public String toString()
{
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
package com.alibaba.beans;
public class Car
{
private String company;
private String brand;
private int maxSpeed;
private float price;
public Car(String company, String brand, float price)
{
super();
this.company = company;
this.brand = brand;
this.price = price;
}
public Car(String company, String brand, int maxSpeed)
{
super();
this.company = company;
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public Car(String company, String brand, int maxSpeed, float price)
{
super();
this.company = company;
this.brand = brand;
this.maxSpeed = maxSpeed;
this.price = price;
}
@Override
public String toString()
{
return "Car [company=" + company + ", brand=" + brand + ", maxSpeed=" + maxSpeed + ", price=" + price + "]";
}
public Car()
{
}
}
这样当Person类去引用Car类时,我们的容器里面也得进行配置。applicationContext里面的代码重新更新如下:
<?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">
<!-- 配置bean -->
<bean id="helloWorld" class="com.alibaba.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
<bean id="car" class="com.alibaba.beans.Car">
<constructor-arg value="bmw" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<shanghai>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>250</value>
</constructor-arg>
</bean>
<bean id="person" class="com.alibaba.beans.Person">
<property name="name" value="Tom"></property>
<property name="age" value="24"></property>
<property name="car" ref="car"></property>
</bean>
</beans>
这里我们要实现Person去访问Car,也就是实现Bean之间的互访,就必须在Bean配置文件中指定对Bean的引用。在Bean的配置文件中可以通过<ref>元素或者ref属性为Bean的属性或者构造器参数指定对Bean的引用。看到上面我们进行了赋值都是单个属性,其实spring里面支持级联属性,这里需要注意的是在Spring里面需要先初始化后才可以为级联属性赋值,否则会有异常,和Struts2不同。
7 集合属性
为了方便,我们重新建一个包以便我们对集合属性进行学习,这里重新建一个包,其包括了Person类和Main类(以便进行集合属性学习),Person里面的car属性变成了集合,即一个人可以有多辆汽车了,对于Person类和容器中的代码得稍作修改了。现在给出这个包下,Person类和Main类(这个类里面只会去获取Person类的实例)。
package com.alibaba.beans.collections;
import java.util.List;
import com.alibaba.beans.Car;
public class Person
{
private String name;
private int age;
private List<Car> cars;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public List<Car> getCars()
{
return cars;
}
public void setCars(List<Car> cars)
{
this.cars = cars;
}
@Override
public String toString()
{
return "Person [name=" + name + ", age=" + age + ", cars=" + cars + "]";
}
}
这个包下的Main类(再次说一遍这个项目里面现在有两个Main类了,运行时只需要点击这个Main类让其运行,我们的目的只是通过这个例子去熟悉集合属性)。
package com.alibaba.beans.collections;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p = (Person) ctx.getBean("person1");
System.out.println(p);
}
}
此时IOC容器里面的配置稍作修改,主要还是为了学习集合属性。
<?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">
<!-- 配置bean -->
<bean id="helloWorld" class="com.alibaba.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
<bean id="car" class="com.alibaba.beans.Car">
<constructor-arg value="bmw" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<shanghai>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>250</value>
</constructor-arg>
</bean>
<bean id="car1" class="com.alibaba.beans.Car">
<constructor-arg value="audi" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<guangzhou>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>260</value>
</constructor-arg>
</bean>
<bean id="person1" class="com.alibaba.beans.collections.Person">
<property name="name" value="Tom"></property>
<property name="age" value="24"></property>
<property name="cars">
<list>
<ref bean="car" />
<ref bean="car1" />
</list>
</property>
</bean>
</beans>
这里做一个小结:在Spring中可以通过一组内置的xml标签(例如:<list>, <set> 或 <map>)来配置集合属性配置java.util.List类型的属性,需要指定<list> 标签,在标签里包含一些元素.这些标签可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用.通过<bean>指定内置Bean定义.通过<null/>指定空元素.甚至可以内嵌其他集合;数组的定义和List一样,都使用<list>;配置java.util.Set需要使用<set>标签,定义元素的方法与List一样。接下来看看Map集合属性的应用。同样为了去学习Map集合属性,需要再次新建一个NewPerson类,然后再applicationContext里面进行更改,最后再第二个新建的Main类中去获取实例进行测试。现在给出代码。
package com.alibaba.beans.collections;
import java.util.Map;
import com.alibaba.beans.Car;
public class NewPerson
{
private String name;
private int age;
private Map<String, Car> cars;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Map<String, Car> getCars()
{
return cars;
}
public void setCars(Map<String, Car> cars)
{
this.cars = cars;
}
@Override
public String toString()
{
return "NewPerson [name=" + name + ", age=" + age + ", cars=" + cars + "]";
}
}
<?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">
<!-- 配置bean -->
<bean id="helloWorld" class="com.alibaba.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
<bean id="car" class="com.alibaba.beans.Car">
<constructor-arg value="bmw" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<shanghai>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>250</value>
</constructor-arg>
</bean>
<bean id="car1" class="com.alibaba.beans.Car">
<constructor-arg value="audi" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<guangzhou>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>260</value>
</constructor-arg>
</bean>
<bean id="person1" class="com.alibaba.beans.collections.Person">
<property name="name" value="Tom"></property>
<property name="age" value="24"></property>
<property name="cars">
<list>
<ref bean="car" />
<ref bean="car1" />
</list>
</property>
</bean>
<span style="color:#FF0000;"><!-- 配置Map属性 --></span>
<bean id="newPerson" class="com.alibaba.beans.collections.NewPerson">
<property name="name" value="micel"></property>
<property name="age" value="25"></property>
<property name="cars">
<map>
<entry key="AA" value-ref="car"></entry>
<entry key="BB" value-ref="car1"></entry>
</map>
</property>
</bean>
</beans>
package com.alibaba.beans.collections;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p = (Person) ctx.getBean("person1");
System.out.println(p);
System.out.println("---------------------我是分割线-------------------------");
NewPerson np = (NewPerson) ctx.getBean("newPerson");
System.out.println(np);
}
}
在此对Map集合属性进行一个小结:(1)Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签.每个条目包含一个键和一个值;(2)可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;Bean引用通过key-ref和value-ref属性定义;
8 使用p命名空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性。使用p命名空间后,基于XML的配置方式将进一步简化。下面在容器中新建一个person2的bean,然后使用p命名空间去定义,最后去第二次定义的Main里面获取实例进行测试。下面给出代码。
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置bean -->
<bean id="helloWorld" class="com.alibaba.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
<bean id="car" class="com.alibaba.beans.Car">
<constructor-arg value="bmw" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<shanghai>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>250</value>
</constructor-arg>
</bean>
<bean id="car1" class="com.alibaba.beans.Car">
<constructor-arg value="audi" type="java.lang.String"></constructor-arg>
<constructor-arg type="java.lang.String">
<value><![CDATA[<guangzhou>]]></value>
</constructor-arg>
<constructor-arg type="int">
<value>260</value>
</constructor-arg>
</bean>
<bean id="person1" class="com.alibaba.beans.collections.Person">
<property name="name" value="Tom"></property>
<property name="age" value="24"></property>
<property name="cars">
<list>
<ref bean="car" />
<ref bean="car1" />
</list>
</property>
</bean>
<!-- 配置Map属性 -->
<bean id="newPerson" class="com.alibaba.beans.collections.NewPerson">
<property name="name" value="micel"></property>
<property name="age" value="25"></property>
<property name="cars">
<map>
<entry key="AA" value-ref="car"></entry>
<entry key="BB" value-ref="car1"></entry>
</map>
</property>
</bean>
<span style="color:#FF0000;"><!-- 通过p命名空间为bean的属性赋值,需要先导入p命名空间,相对于传统的配置方式比较简洁 --></span>
<bean id="person2" class="com.alibaba.beans.collections.Person" p:name="Jackon"
p:age="30">
</bean>
</beans>
由于博客篇幅太长了,接下来的内容会在《Spring学习总结一(续1)》里面,可以参看本人有关Spring的博客分类。本篇博客参照了尚硅谷的佟刚视频和李刚的JavaEE轻量级开发一书,所有的代码将在总结一完结时给出下载地址。