Spring
概述
- 最受欢迎的企业级 Java 应用程序开发框架
- 一个开源的 Java 平台
- 轻量级的框架,基础版本只有2M左右
- 框架的核心特性是可以用于开发任何 Java 应用程序
三层架构
- 表现层:Web层 MVC是表现层的一个设计模型
- 业务层:Service层
- 持久层:dao层
特性
非侵入式、控制反转(IOC)、依赖注入、面向切面(AOP)、容器、组件化、一站式。。。
参考文章:W3Cschool
体系结构
核心容器
- Spring-core:提供了框架的基本组成部分,包括IoC和依赖注入功能。
- Spring-beans:模块提供 BeanFactory,应用工厂模式。
- context:基于core和beans模块。
- Spring-expression:提供了强大的表达式语言,用于在运行时查询和操作对象图。
完整依赖关系如下图所示:
数据访问/集成
数据访问/集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块,它们的细节如下:
ps:JDBC=Java Data Base Connectivity,ORM=Object Relational Mapping,OXM=Object XML Mapping,JMS=Java Message Service
Web
Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成。
- Web 模块提供面向 web 的基本功能和面向 web 的应用上下文,比如多部分(multipart)文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分。
- Web-MVC 模块为 web 应用提供了模型视图控制(MVC)和 REST Web服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成。
- Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
- Web-Portlet 模块提供了用于 Portlet 环境的 MVC 实现,并反映了 spring-webmvc 模块的功能。
Test、其他
…
IoC容器
Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。(这些对象被称为 Spring Beans)
在Spring出现之前,通常new(实例化)一个实例是由程序员来完成,而“控制反转”就是将new 实例的操作交给Spring容器来做。(BeanFactory在Spring中就是IoC容器的实际代表者)
Bean定义
bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。
创建Bean
创建实体类
1
2
3
4
5
6
7
public class PC {
private String name;
public PC(){ System.out.println("PC实例化"); }
public void startUp(){ System.out.println(this.name+"正在启动"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
添加applicationContext.xml文件(该文件名可随便起,一般为applicationContext.xml)
1
2
3
4
5
6
7
8
<?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 id="pc" class="top.jamartin.spring.PC">
<property name="name" value="我的电脑" />
</bean>
</beans>
Spring中两种不同类型的容器
- Spring BeanFactory:给DI(依赖注入)提供最基本的支持。
- Spring ApplicationContext:该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。
BeanFactory
主要的功能就是为DI(依赖注入)提供支持,该容器接口在org.springframework.beans.factory.BeanFactory中被定义。
ps:在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
通过XmlBeanFactory生成工厂:(已过时)
1
2
3
4
5
6
@Test
public void TestXmlBeanFactory(){
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
PC pc = factory.getBean("pc", PC.class);
pc.startUp();
}
ApplicationContext
Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文。
ps:ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:
- FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
- ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
这里举例通过ClassPathXmlApplicationContext生成工厂bean。
实体类和applicationContext.xml文件和之前一样,只需要改测试类:
1
2
3
4
5
6
@Test
public void TestClassPathXmlApplicationContext(){
Application factory = new ClassPathXmlApplicationContext("applicationContext.xml");
PC pc = factory.getBean("pc", PC.class);
pc.startUp();
}
结果都是一样的:
Bean的作用域
作用域的属性为scope
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
举例:
将刚刚的applicationContext.xml文件中的bean标签更改一下
1
2
3
4
<bean id="pc" class="top.jamartin.spring.PC"
scope="singleton">
<property name="name" value="我的电脑" />
</bean>
在测试类中多new一个实例
1
2
3
4
5
6
7
8
@Test
public void TestScope(){
ApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");
PC pc = factory.getBean("pc", PC.class);
pc.startUp();
PC pc2 = factory.getBean("pc",PC.class);
pc2.startUp();
}
PC只实例化了一次
再将 “singleton” 改为 ”prototype” 重新测试一下:
Bean的生命周期
首先,在PC类中添加两个方法:init()和destory()
1
2
3
4
5
6
7
8
9
public class PC {
...
public void init(){
System.out.println("init");
}
public void destory(){
System.out.println("destory");
}
}
然后更改配置文件
1
2
3
4
5
<bean id="pc" class="top.jamartin.spring.PC"
scope="singleton"
init-method="init" destory-method="destory" >
<property name="name" value="我的电脑" />
</bean>
在测试类中实例化一个PC
1
2
3
4
5
6
@Test
public void TestScope(){
ApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");
PC pc = factory.getBean("pc", PC.class);
pc.startUp();
}
依赖注入
Spring 框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。
基于构造函数的依赖注入
还是通过PC来举例子:
一个PC(电脑)一般需要配一些硬件设备才能使用,例如:鼠标键盘、硬盘等。
先给PC加一个HardWare(硬盘)
1
2
3
4
5
6
7
8
public class HardWare {
private String name;
public void read(){ System.out.println("读取数据"); }
public void write(){ System.out.println("写数据"); }
public HardWare() { System.out.println("HardWare has constructor"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
然后在PC中引入HardWare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PC {
private String name;
//引入硬盘
private HardWare hd;
public PC(HardWare hd){
System.out.println("Inside HardWare contructor");
this.hd = hd;
}
public PC(){ System.out.println("PC实例化"); }
public void startUp(){
System.out.println(this.name+"正在启动");
hd.read();
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
编辑测试类:
1
2
3
4
5
public void TestConstruct(){
ApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");
PC pc = factory.getBean(PC.class);
pc.startUp();
}
运行测试类:
可以看到,硬盘实例化后,被注入到了PC中,这就叫做依赖注入。
基于设值函数的依赖注入
以刚刚的例子,硬盘类不变,PC类更为如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//PC类
public class PC {
private String name;
//引入硬盘
private HardWare hd;
public HardWare getHd() {
return hd;
}
public void setHd(HardWare hd) {
this.hd = hd;
System.out.println("Setter HardWare")
}
public PC(){ System.out.println("PC实例化"); }
public void startUp(){
System.out.println(this.name+"正在启动");
hd.read();
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
运行测试类:
注入集合
在上面的例子中介绍了如何注入 Beans,但是如果想要传递多个值,例如List
、Set、Map等,该怎么做呢?(Spring 提供了四种类型的集合的配置元素)
元素 | 描述 |
---|---|
<list> | 它有助于连线,如注入一列值,允许重复。 |
<set> | 它有助于连线一组值,但不能重复。 |
<map> | 它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。 |
<props> | 它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。 |
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.tutorialspoint;
import java.util.*;
public class JavaCollection {
private List addressList;
private Set addressSet;
private Map addressMap;
public void setAddressList(List addressList) {
this.addressList = addressList;
}
public List getAddressList() {
System.out.println("List Elements :" + addressList);
return addressList;
}
public void setAddressSet(Set addressSet) {
this.addressSet = addressSet;
}
public Set getAddressSet() {
System.out.println("Set Elements :" + addressSet);
return addressSet;
}
public void setAddressMap(Map addressMap) {
this.addressMap = addressMap;
}
public Map getAddressMap() {
System.out.println("Map Elements :" + addressMap);
return addressMap;
}
}
配置文件applicationContext.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<bean id="js" class="top.jamartin.JavaSet">
<!-- results in a setAddressList(java.util.List) call -->
<property name="addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</list>
</property>
<!-- results in a setAddressSet(java.util.Set) call -->
<property name="addressSet">
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</set>
</property>
<!-- results in a setAddressMap(java.util.Map) call -->
<property name="addressMap">
<map>
<entry key="1" value="INDIA"/>
<entry key="2" value="Pakistan"/>
<entry key="3" value="USA"/>
<entry key="4" value="USA"/>
</map>
</property>
</bean>
编写测试类:
1
2
3
4
5
6
7
8
@Test
public void TestCollection(){
ApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");
JavaSet js = factory.getBean(JavaSet.class);
System.out.println(js.getAddressList());
System.out.println(js.getAddressMap());
System.out.println(js.getAddressSet());
}
执行测试类:
Spring Beans 自动装配
自动装配ByName
还是PC机的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//PC类
public class PC {
private String name;
//引入硬盘
private HardWare hd;
public HardWare getHd() {
return hd;
}
public void setHd(HardWare hd) {
this.hd = hd;
System.out.println("Setter HardWare")
}
public PC(){ System.out.println("PC实例化"); }
public void startUp(){
System.out.println(this.name+"正在启动");
hd.read();
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
如果需要通过ByName自动装配,则配置文件中pc 就需要添加一个autowire属性:
<beans>
<bean id="pc" class="top.jamartin.PC" autowire="byName">
<property name="name" value="我的电脑" />
</bean>
<bean id="hardware" class="top.jamartin.HardWare" />
</beans>
ByName : 会自动在 beans.xml(容器)的上下文中查找 和自己对象中 set方法 set后面的值对应的 bean 的 id或name。
需要确保配置文件下有一个bean中的id/name与调用Bean的属性相同。
自动装配ByType
<beans>
<bean id="pc" class="top.jamartin.PC" autowire="byType">
<property name="name" value="我的电脑" />
</bean>
<bean id="HardWare" class="top.jamartin.HardWare" />
</beans>
byType:会自动在beans.xml(容器)上下文中查找,和自己 对象的属性类型 相同的 bean。
由构造函数自动装配
在PC机中加入带有硬盘参数的构造函数:
1
2
3
4
5
6
7
8
9
//PC类
public class PC {
...
private HardWare hd;
public PC(HardWare hd){
this.hd = hd;
}
...
}
如果需要通过Constructor自动装配,则配置文件中pc 就需要添加一个autowire属性:
<beans>
<bean id="pc" class="top.jamartin.PC" autowire="constructor">
<constructor-arg value="Generic Text Editor"/>
</bean>
<bean id="HardWare" class="top.jamartin.HardWare" />
</beans>