首页 > 技术文章 > Spring是一个分层的Java

chenergougou 2017-06-12 15:23 原文

Spring简介

Spring是一个分层的Java SE/EE应用一站式的轻量级开源框架。Spring核心是IOC和AOP。
Spring优点

-方便解耦,简化开发,通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码造成的程序耦合度高。

-AOP编程的支持,通过Spring提供的AOP功能,方便进行面向切面编程。

-声明式事务的支持,在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

-方便程序的测试,可以用非容器依赖的编程方式进行几乎所有的测试工作。

-方便集成各种优秀框架,Spring提供了对各种优秀框架的直接支持。
Spring的体系结构

这里写图片描述

整个spring框架按其所属功能可以划分为五个主要模块,这五个模块几乎为企业应用提供了所需的一切,从持久层、业务层到表现层都拥有相应的支持,这就是Spring为什么是一站式框架。IoC和AOP是Spring的核心。

-核心模块(Core Container)

Spring的核心模块实现了IoC的功能,它将类和类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述。 由IoC容器负责类的创建,管理,获取等。BeanFactory接口是Spring框架的核心接口,实现了容器很多核心的功能。

Context模块构建于核心模块之上,扩展了BeanFactory的功能,包括国际化,资源加载,邮件服务,任务调度等多项功能。ApplicationContext是Context模块的核心接口。

表达式语言(Expression Language)是统一表达式语言(EL)的一个扩展,支持设置和获取对象属性,调用对象方法,操作数组、集合等。使用它可以很方便的通过表达式和Spring IoC容器进行交互。

-AOP模块

Spring AOP模块提供了满足AOP Alliance规范的实现,还整合了AspectJ这种AOP语言级的框架。通过AOP能降低耦合。
-数据访问集成模块(Data Access/Integration )

该模块包括了JDBC、ORM、OXM、JMS和事务管理

事务模块:该模块用于Spring管理事务,只要是Spring管理对象都能得到Spring管理事务的好处,无需在代码中进行事务控制了,而且支持编程和声明性的事务管理。

JDBC模块:提供了一个JBDC的样例模板,使用这些模板能消除传统冗长的JDBC编码还有必须的事务控制,而且能享受到Spring管理事务的好处。

ORM模块:提供与流行的“对象-关系”映射框架的无缝集成,包括hibernate、JPA、MyBatis等。而且可以使用Spring事务管理,无需额外控制事务。

OXM模块:提供了一个对Object/XML映射实现,将Java对象映射成XML数据,或者将XML数据映射成java对象,Object/XML映射实现包括JAXB、Castor、XMLBeans和XStream。

JMS模块:用于JMS(Java Messaging Service),提供一套 “消息生产者、消息消费者”模板用于更加简单的使用JMS,JMS用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

-Web模块

该模块建立在AoolicationContext模块之上,提供了Web应用的功能。如文件上传、FreeMarker等。
Spring可以整合Struts2等MVC框架。Spring自己提供了MVC框架Spring MVC。
-测试模块

Spring可以用非容器依赖的编程方式进行几乎所有的测试工作,支持JUnit和TestNG等测试框架。
Spring的IoC容器详解

我们首先来讲解一下IoC的概念。
IoC(控制反转:Inverse of Control)是Spring容器的核心。但是IoC这个概念却比较晦涩,让人不太容易望文生义。

IoC控制反转和DI依赖注入

传统程序设计中,我们需要使用某个对象的方法,需要先通过new创建一个该对象,我们这时是主动行为。而IoC是我们将创建对象的控制权交给IoC容器,这时是由容器帮忙创建及注入依赖对象,我们的程序被动的接受IoC容器创建的对象,控制权反转,所以叫控制反转。

因为IoC确实不够开门见山,所以提出了DI(依赖注入:Dependency Injection)的概念
即让第三方来实现注入,以移除我们类与需要使用的类之间的依赖关系。

IoC是目的,DI是手段。我们为了实现IoC,让生成对象的方式由传统方式(new)反转过来,需要创建相关对象时由IoC容器帮我们注入。(DI)

简单的说,就是我们类里需要另一个类,只需要让Spring帮我们创建 ,这叫做控制反转,然后Spring帮我们将需要的对象设置到我们的类中,这叫做依赖注入。
注入方法

使用有参构造方法注入
public class User{
private String name;
public User(String name){
this.name=name;
}
}

User user=new User("tom");
使用属性注入
通过属性的set方法进行注入。

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

User user=new User();
user.setName("jack");
使用接口注入
将调用类所有依赖注入的方法抽取到接口中,调用类通过实现该接口提供相应的注入方法。

public interface Dao{
public void delete(String name);
}

public class DapIml implements Dao{
private String name;
public void delete(String name){
this.name=name;
通过容器完成依赖关系的注入
上面的注入方式都需要我们手动的进行注入,如果有一个第三方容器能帮助我们完成类的实例化,以及依赖关系的装配,而我们只需要专注于业务逻辑的开发即可。Spring就是这样的容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入的工作。

Spring的IoC例子

一 .创建工程,导入jar包

这里我们只是做IoC的操作,所以只需要导入核心模块里的jar包,beans,core,context,expression等
因为spring中并没有日志相关的jar包,所以我们还需要导入log4j和commons-logging
二 .创建一个类

public class User {
public void add(){
System.out.println("add.....");
}
}
5
三 .创建一个类
创建一个xml配置文件,放在src目录下,名字可以随便取
<?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="user" class="com.cad.domain.User"></bean>
</beans>
四 .进行测试
//这只是用来测试的代码,后期不会这么写
public class Test {

@org.junit.Test
public void test(){
//加载配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//获取对象
User user=(User) context.getBean("user");
System.out.println(user);
//调用方法
user.add(www.sb45475.com);
}
}
在容器启动时,Spring会根据配置文件的描述信息,自动实例化Bean并完成依赖关系的装配,从容器中即可获得Bean实例,就可以直接使用。Spring为什么仅凭一个简单的配置文件,就能神奇的实例化并配置好程序使用的Bean呢?

答案是通过Java的反射技术。

Spring的DI例子

我们的service层总是用到dao层,以前我们总是在Service层new出dao对象,现在我们使用依赖注入
Service层依赖dao层。

UserDao
public class UserDao {
public void add(){
System.out.println("dao.....");
}
}

UserService
public class UserService {
UserDao userdao;
public void setUserdao(UserDao userdao){
this.userdao=userdao;
}

public void add(){
System.out.println("service.......");
userdao.add();
}
}

配置文件
<bean id="userdao" class="com.cad.domain.UserDao"></bean>
//这样在实例化service的时候,同时装配了dao对象,实现了依赖注入
<bean id="userservice" class="com.cad.domain.UserService">
//ref为dao的id值
<property name="userdao" ref="userdao"></property>
Spring的资源访问神器

JDK提供的访问资源的类(如java.NET.URL,File)等并不能很好很方便的满足各种底层资源的访问需求。Spring设计了一个Resource接口,为应用提供了更强的访问底层资源的能力,该接口拥有对应不同资源类型的实现类。

Resource接口的主要方法

boolean exists():资源是否存在

boolean isOpen():资源是否打开

URL getURL():返回对应资源的URL

File getFile():返回对应的文件对象

InputStream getInputStream():返回对应资源的输入流

Resource在Spring框架中起着不可或缺的作用,Spring框架使用Resource装载各种资源,包括配置文件资源,国际化属性资源等。
为了访问不同类型的资源,必须使用相应的Resource实现类,这是比较麻烦的。Spring提供了一个强大的加载资源的机制,仅通过资源地址的特殊标识就可以加载相应的资源。

首先,我们了解一下Spring支持哪些资源类型的地址前缀

classpath: 例如classpath:com/cad/domain/bean.xml。从类路径中加载资源

file:例如 file:com/cad/domain/bean.xml.使用UrlResource从文件系统目录中加载资源。

http:// 例如http://www.caihonyule.com /resource/bean.xml 使用UrlResource从web服务器加载资源

ftp:// 例如frp://www.jpg521.com /bean.xml 使用UrlResource从ftp服务器加载资源

Spring定义一套资源加载的接口。ResourceLoader接口仅有一个getResource(String location)的方法,可以根据资源地址加载文件资源。资源地址仅支持带资源类型前缀的地址,不支持Ant风格的资源路径表达式。ResourcePatternResolver扩展ResourceLoader接口,定义新的接口方法getResources(String locationPattern) ,该方法支持带资源类型前缀以及Ant风格的资源路径的表达式。
PathMatchingResourcePatternResolver是Spring提供的标准实现类。

BeanFactory和ApplicationContext

Spring通过一个配置文件描述Bean与Bean之间的依赖关系,通过Java语言的反射技术能实例化Bean并建立Bean之间的依赖关系。Spring的IoC容器在完成这些底层工作的基础上,还提供了bean实例缓存、生命周期管理、事件发布,资源装载等高级服务。

BeanFactory是Spring最核心的接口,提供了高级IoC的配置机制。ApplicationContext建立在BeanFactory的基础上,提供了更多面向应用的功能。我们一般称BeanFactory为IoC容器,ApplicationContext为应用上下文,也称为Spring容器。

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

BeanFactory介绍

BeanFactory是一个类工厂,和传统的类工厂不同,传统的类工厂仅负责构造一个类或几个类的实例。而BeanFactory可以创建并管理各种类的对象,Spring称这些被创建和管理的Java对象为Bean。

BeanFactory是一个接口,Spring为BeanFactory提供了多种实现,最常用的就是XmlBeanFactory。
BeanFactory接口最主要的方法就是getBean(String beanName),该方法从容器中返回指定名称的Bean。
BeanFactory接口的功能通过其他实现它的接口不断扩展。

例子:

我们使用Spring配置文件为User类提供配置信息,然后通过BeanFactory装载配置文件,启动Spring IoC容器。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.yuheng119.com/ /2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="user" class="com.cad.domain.User"></bean>
我们通过XmlBeanFactory实现类启动Spring IoC容器

public class Test {

@org.junit.Test
public void test(){
//获取配置文件
ResourcePatternResolver resolver=new PathMatchingResourcePatternResolver();
Resource rs=resolver.getResource(www.yigouylpt2.com classpath:bean.xml");

//加载配置文件并启动IoC容器
BeanFactory bf=new XmlBeanFactory(rs);

//从容器中获取Bean对象
User user=(User) bf.getBean("user");

user.speak();
}
XmlBeanFactory装在Spring配置文件并启动IoC容器。通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化创建动作在第一个调用时。
在初始化BeanFactory,必须提供一种日志框架,我们使用Log4J。

ApplicationContext介绍

ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要编程方式来实现,ApplicationContext中可以通过配置的方式来实现。

ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统中加载配置文件。

和BeanFactory初始化相似,ApplicationContext初始化也很简单。

ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext的初始化和BeanFactory初始化有一个重大的区别,BeanFactory初始化容器时,并未初始化Bean,第一次访问Bean时才创建。而ApplicationContext则在初始化时就实例化所有的单实例的Bean。因此ApplicationContext的初始化时间会稍长一点。

WebApplicationContext介绍

WebApplicationContext是专门为Web应用准备的,它允许以相对于Web根目录的路径中加载配置文件完成初始化工作。从WebApplicationContext中可以获取ServletContext的引用,整个WebApplicationContext对象作为属性放置到ServletContext中,以便Web应用环境中可以访问Spring应用上下文。

ConfigurableWebApplicationContext扩展了WebApplicationContext,允许通过配置方式实例化WebApplicationContext,定义了两个重要方法。

setServletContext(ServletContext servletcontext):为Spring设置ServletContext

setConfigLocation(String[] configLocations):设置Spring配置文件地址。

WebApplicationContext的初始化
在ServletContext对象创建的时候,借助监听器监听ServletContext对象的创建,然后初始化WebApplicationContext对象。Spring提供了这个监听器。ContextLoaderListener。我们只需要在web.xml配置监听器即可。

父子容器

通过HierarchicalBeanFactory接口,Spring的IoC容器可以建立父子层级关联的体系,子容器可以访问父容器的Bean,父容器不能访问子容器的Bean。

Spring使用父子容器实现了很多功能,比如在Spring MVC中,表现层Bean位于子容器中,业务层和持久层Bean位于父容器中,这样,表现层Bean就可以引用持久层和业务层的Bean,而业务层和持久层就看不到表现层的Bean。

在IoC容器中装配Bean基于XML

为了让IoC容器帮我们创建和管理对象,我们必须在Spring IoC容器中装配好Bean,并建立好Bean和Bean之间的关联关系。

Spring配置概述

Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系。

对于基于XML的配置,Spring2.0以后配置文件使用Schema格式。

我们可以在我们下载的Spring jar包中找到相关Schema文件的配置。

spring-framework-4.3.8.RELEASE-dist\spring-framework-4.3.8.RELEASE\docs\spring-framework-reference\html\ xsd-configuration.html

Bean的基本配置

< bean id=”userdaoid” class=”UserDao”>< /bean >
id:Bean的名称
class:指定了Bean对应的实现类

Bean的命名
一般情况下,配置Bean时需要为其指定一个id属性作为Bean的名称。id在IoC容器中必须是唯一的。id的命名需要满足xml的命名规范:必须以字母开始,后面可以使字母数字、下划线、句号、冒号等符号,逗号和空格等字符是非法的。

< bean>还有一个name属性,和id属性作用一样,但是name几乎可以使用任何字符。

Spring配置文件中不允许出现相同的id,却允许出现相同的name。如果有相同的name,通过getBean(beanName)获取Bean时,将返回最后生命的Bean,原因是最后的Bean覆盖了前面同名的Bean。应该尽量使用id。

Bean的实例化方式

使用默认的无参构造方法实例化
创建一个类
public class User{
}

配置xml文件

<bean id="user" class="com.cad.domain.User" www.huazongyule.com/ ></bean>
使用实例工厂方法实例化
工厂方法是非静态的,即必须实例化工厂类后才能调用工厂方法。

创建一个工厂类,提供一个普通的方法,返回指定的对象
public class UserFactory {
public User getUser(){
return new User();
}
}
配置xml文件

//先要实例工厂
<bean id="factory" class="com.cad.domain.UserFactory" ></bean>
//通过实例化工厂调用工厂方法获取对象,factory-bean属性引用工厂实例,factory-method指定工厂方法
<bean id="user" factory-bean="factory" factory-method="getUser"></bean>
使用静态工厂方法实例化
工厂类方法是静态的,可以不用创建工厂类实例直接使用。

创建一个工厂类,提供一个静态方法,返回指定的对象
public class UserFactory {
public static User getUser(){
return new User();
}
配置xml文件
//class指定工厂类,factory指定工厂方法
<bean id="user" class="com.cad.domain.UserFactory" factory-method="getUser"></bean>
Bean的属性注入

Spring支持两种依赖注入方式,分别是属性注入和构造方法注入。用户不但可以将String,int等类型参数注入到Bean中,还可以将集合、Map类型注入到Bean中。

属性注入(使用属性的set方法)
属性注入要求Bean提供一个默认的构造方法,并为需要注入的属性提供对应的set方法。Spring调Bean的默认构造方法创建对象,然后通过反射的方式调用set方法注入属性。

创建类
public class User {
private String username;
public void setUsername(String username){
this.username=username;
}
public void speak(){
System.out.println("my name is"+username);
}
}

配置文件
<bean id="user" class="com.cad.domain.User">
<!--<property>对应一个属性,name是属性的名称,value是属性的值,ref用来引用其他bean-->
<property name="username" value="张三"></property>
</bean>

使用有参数构造方法注入
使用构造方法注入前提是Bean必须提供带参的构造方法。

创建User类
public class User {
private String username;
public User(String username){
this.username=username;
}
public void speak(){
System.out.println("my name is"+username);
}
}

配置文件
<bean id="user" class="com.cad.domain.User">
<!--<constructor-arg>标签对应构造方法的参数,name是参数名称,value是参数值-->
<constructor-arg name="username" value="jack"></constructor-arg>
</bean>


=============================================================
这里有一个小问题,如果类中有两个带参构造方法,我们应该怎样注入?
<constructor-arg>标签有两个属性 index和type。
type是指定要注入的属性的类型,index指定注入的属性的顺序,从0开始。
注入null值
如果需要为某个属性注入null值,必须使用专用的<null/>标签。

<bean id="userdaoid" class="UserDao">
<property name="username"><null/></property>
</bean>
注入集合类型
java.util包中的集合类是最常用的数据结构类型。主要包括List、Set、map、Properties等,Spring为这些集合提供了专门的标签来配置。

我们创建一个类,包含了各种集合类型

public class CollectionDemo {
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}

public String toString() {
return "CollectionDemo [list=" + list + ", set=" + set + ", map=" + map + ", properties=" + properties + "]";
}

配置文件

<bean id="collid" class="com.cad.domain.CollectionDemo">
<!--配置list集合-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>

<!--配置set集合-->
<property name="set">
<set>
<value>java</value>
<value>c#</value>
</set>
</property>

<!--配置map集合-->
<property name="map">
<map>
<entry key="jack" value="杰克"></entry>
<entry key="tom" value="汤姆"></entry>
</map>
</property>

<!--配置properties类型-->
<property name="properties">
<props>
<prop key="prop1">prop1</prop>
<prop key="prop2">prop2</prop>
</props>
</property>


</bean>
使用p命名空间

为了简化Xml的配置,Spring引入了一个P命名空间。

未使用P命名空间之前
创建一个User类
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void say(){
System.out.println(name+":"+age);
}
使用P命名空间
<?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" //声明p命名空间
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


<!--使用p:属性名="xxx" 或者 p:属性名-ref="引用id"来指定属性-->
<bean id="user" class="com.cad.domain.User" p:name="李四" p:age="88">
</bean>

</beans>
Bean的作用域

Bean的作用域会对Bean的生命周期和创建方式产生影响。

这里写图片描述

我们来演示一下,将User类的作用域设置为singleton
<bean id="user" class="com.cad.domain.User" p:name="李四" p:age="88" scope="singleton">
</bean>


public class Test {

@org.junit.Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
User user1=(User) ac.getBean("user"); //获得的两个对象是一样的
User user2=(User) ac.getBean("user");
System.out.println(user1);
System.out.println(user2);
}

}

输出
com.cad.domain.User@1c3a4799
com.cad.domain.User@1c3a4799
配置
<bean id="userfactory" class="com.cad.domain.UserFactory"></bean>
测试
public class Test {

@org.junit.Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
User user=(User) ac.getBean("userfactory");
user.say();
}

}

输出 tom:14
当我们< bean >标签的class属性配置的类实现了FactoryBean接口时,通过getBean返回的就不是该类本身,而是getObject()方法所返回的对象,相当于getObject()方法代理了getBean().

推荐阅读