上一章 详解SSJ(Spring3.x mvc + Spring3.x Core + JPA2.x)轻量级集成开发—序章
下一章 详解SSJ(Spring3.x mvc + Spring3.x Core + JPA2.x)轻量级集成开发—第2章 剖析IOC容器进阶
目录
一、Spring是什么?
二、Spring的优点
三、下载Spring
四、使用传统工程构建Spring应用
五、使用Maven3.x工程构建Spring应用
六、IOC容器与设计模式
七、依赖注入
前言
上一章节笔者仅仅只是讲解一些涉及到后续章节的概念,那么从本章开始,笔者将会为大家带来来类博文的后续连载。这里首先笔者要声明,由于笔者近期工作较忙,可能后续博文的连载时间会偶尔出现不规律的情况,希望大家能够体谅,笔者在此也衷心祝愿大家在新的一年能够事事顺利。
一、Spring是什么?
Spring已经逐渐从单纯的一种技术规范实现慢慢的蜕变为企业的一站式解决方案?是的,如今整个领域模型中几乎随处可见Spring的身影,Spring贯穿了效验层、控制层、业务层甚至是持久层...笔者接触Spring至今也有多年左右了,希望本文的一些观点不会成为某些高手所为的妙论...
其实你完全可以将Spring理解为一个载满各种高精尖武器的“航母平台”,其平台上面承载着各式各样,足以令你眼花缭乱但却实用的技术,其覆盖率之广。如果你想用mvc,Spring为你提供有支持RESTful风格的mvc Framework,或者允许与第三方mvc 产品进行无缝整合。如果你想使用Spring的核心业务,Spring最新的3.x版本将带给你全新的体验。如果你想对权限进行控制,Spring为你提供有Spring security。如果你不想手动管理你的持久层事物,Spring为你提供有各式各样的第三方ORM 产品的整合方案,甚至还为你提供有高效的Spring JDBC(Spring JDBC仅仅只是针对传统的JDBC做了轻量级封装,熟悉JDBC的开发人员上手应该是很快的,并不会觉得复杂与麻烦)。如果你是Android的开发人员,Spring照样为你提供有基于Mobile领
域的各种规范实现。当然Spring所带来的影响力同样也是巨大的,时至今日Spring更像是驱动技术市场脚步的一匹黑马,越来越多的厂商阵营,越来越多的规范性规划,都在向Spring靠拢。
听了这么多,你可能会觉得Spring就是一个威力超强的高效粘合剂,不论是Spring原生的还是第三方的,Spring都可以将其进行快速且无缝的整合,一切都显得是那么顺其自然,浑然天成。Spring的最新版本为3.x,在以往版本的基础之上,Spring大幅度的简化了开发人员的编码量,更清晰的定位了配置文件的作用以及更多的Annotations支持,使得开发Spring应用将会成为一件很美妙的事情。
注意:
在很多年前,几乎所有的开发人员都希望将可配置型代码写进配置文件中,因为这样除了可以很好的解决代码耦合的问题,同样也可以解决系统每次更改属性都要反复部署的问题。但随着时间的推移,咱们的配置文件几乎越来越庞大,文件中的内容越来越冗长,为了很好解决配置文件的瘦身问题,越来越多的开源项目又开始将配置文件中的内容移至代码中,Spring其实就是其中一个。虽然这种做法在理论上看来无非是重量级的操作,但绝非是高侵入式的,因为很多开源项目并不是直接将第三方代码侵入进咱们的业务逻辑中,而只是以Annotation的方式进行嵌入。这种侵入级别而言对于开发人员来说并不是不能接受的,并且也是某些开源项目的目标,就像.net平台一样,始终都允许只用微软的东西,说不定某些开源项目拥有技术垄断的商业嫌疑(开个玩笑)。
二、Spring的优点
早在Spring框架没有出现之前,我们可以通过使用工厂模式、代理模式来达到降低程序中各个组件之间的耦合度。但Spring的出现将为J2EE企业级应用带来更好的复用性、维护性、扩展性以及伸缩性,Spring框架具有如下优点:
1、低侵入式设计,业务逻辑与Spring依赖极低;
2、独立且不依赖任何Web容器,可适用于任何的Java应用;
3、Spring的Ioc容器降低了程序中各个组件之间的耦合度;
4、Spring的AOP容器为程序提供了更好的复用方式;
5、Spring可以很好的与第三方Framework产品进行整合;
6、Spring的高度开放性,可以使开发者可以自由选择Spring的子集或者全部功能;
三、下载Spring
笔者本文所有程序示例均使用Spring3.1.0.RC2本版,大家可以登陆Spring的官方站点:http://www.springframework.org下载最新的Spring构件。当你成功下载并解压后,你可以在dist目录下找到Spring的相关构件。
四、使用传统工程构建Spring应用
由于考虑到Spring独立且不依赖于任何Web容器,所以我们将在本章开始使用Spring构建一个简单的Spring应用。在开始构建前,我们首先创建一个名称为spring-test的项目,然后我们再将Spring的一些核心构件导入至刚刚建好的项目中,这些基础构件包括:
1、commons-logging.jar;
2、log4j-1.2.16.jar;
3、org.springframework.asm-3.1.0.RC2.jar;
4、org.springframework.beans-3.1.0.RC2.jar;
5、org.springframework.context-3.1.0.RC2.jar;
6、org.springframework.core-3.1.0.RC2.jar;
7、org.springframework.expression-3.1.0.RC2.jar;
我们首先来编写主程序,该程序的目的其实很简单,仅仅只是用于初始化Spring容器(IOC容器)。你要明白的是Spring的核心便是IOC,该容器用于负责及管理容器内部所有的Java组件,该程序代码如下:
public class ClientTest
{
/**
* @param args
*/
public static void main(String[] args) throws Exception
{
/* 加载Ioc配置文件,并初始化Ioc容器 */
ApplicationContext context = new ClassPathXmlApplicationContext(
"root-context.xml");
}
}
org.springframework.context包和org.springframework.beans包是IOC容器的核心包,这2个包中包含了IOC容器大量的的依赖构件。其中org.springframework.beans包下的BeanFactory接口是IOC容器的顶层抽象接口,该接口负责IOC容器的初始化工作。并且为开发者提供了多种BeanFactory接口的实现,XmlBeanFactory就是其中一个。
当然org.springframework.context包下的ApplicationContext接口同样也派生自BeanFactory接口,并且ApplicationContext进一步扩展了BeanFactory接口。比如:更易与Spring AOP集成、国际化处理、事件传递及各种不同应用层的上下文实现。
由于ApplicationContext完全派生自BeanFactory接口,也就是说BeanFactory所具备的特征,ApplicationContext同样具备,并且还拥有一些独有特性,所以一般开发者多选择ApplicationContext作为IOC容器接口。
提示:
使用ApplicationContext加载多个配置文件示例:
/* 加载多个配置文件 */
ApplicationContext context1 = new ClassPathXmlApplicationContext(
new String[]{"a-context.xml", "b-context.xml"});
/* 使用通配符加载多个配置文件 */
ApplicationContext context2 = new ClassPathXmlApplicationContext("*-context.xml");
其实Spring的IOC容器就是一个非常巨大的工厂,由它管理着其内部的诸多Java组件,但这个工厂又如何进行管理的呢?先别着急,咱们先来尝试着让IOC容器管理一个简单的Java组件:
/**
* 用户登陆Bean
*
* @author JohnGao
*/
public class LoginBean
{
/* 用户账号, 用户密码 */
private String userAccount, passWord;
/* 此处省略set和get方法 */
}
上述程序示例中,笔者定义了一个标准的JavaBean。并且笔者接下来则要将该类型实例托管给Spring的IOC容器,由Spring来负责创建及维护,下述为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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- 定义LoginBean实例 -->
<bean id="loginBean" class="org.johngao.bean.LoginBean">
<property name="userAccount" value="JohnGao" />
<property name="passWord" value="123456" />
</bean>
</beans>
上述程序示例中,我们通过<bean/>标签定义了LoginBean。其中属性“id”用于定义该Bean的唯一标示,也就是Key,程序中则通过这个Key来访问该Bean的实例。“class”属性标注了指定Bean的实现,这里不能使用抽象,一定需要指明实现。而包含在<bean/>标签内部的<property/>标签则采用投值注入的方式为LoginBean中的两个属性进行了手动初始化,至于什么是投值注入笔者在后续章节会重点讲解。
或许看到这里,你已经明白了笔者为什么要称IOC容器为工厂了。我们可以在Spring配置文件中定义诸多的Bean实例,这些实例的管理将全部交由IOC容器来负责,这使得我们更加专注于自身业务。还有一点IOC容器其实就是采用反射机制对这些定义在配置文件中的Bean进行统一的实例化操作。
修改我们的主程序代码:
public class ClientTest
{
/**
* @param args
*/
public static void main(String[] args) throws Exception
{
/* 加载Ioc配置文件,并初始化Ioc容器 */
ApplicationContext context = new ClassPathXmlApplicationContext(
"root-context.xml");
/* 获取LoginBean实例 */
LoginBean loginBean = (LoginBean) context.getBean("loginBean");
System.out.println(loginBean.getUserAccount());
System.out.println(loginBean.getPassWord());
}
}
上述程序示例中,我们通过ApplicationContext的getBean()方法从IOC容器中获取了LoginBean的实例。如果你是第一次接触Spring,上述程序示例可能会让你产生些郁闷,不过没关系,笔者在后续章节将会为你揭开你的诸多疑问。
五、使用Maven工程构建Spring应用
随着Maven的逐渐普及,大多数开源项目都已经准备或者正在使用Maven来对其进行管理。如果你从未接触过Maven,甚至不清楚Maven是做什么的,那就请你先阅读笔者的<Use Maven3.x>系列博文。因为在后续章节中,笔者所有的程序示例将完全采用Maven3.x来进行构建。
如果想使用Maven来进行构建我们的Spring应用,其最主要的就是Spring的构件依赖管理,下述为Spring的构件依赖配置:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> </dependencies>
提示:
本章内容仅仅只是展示Spring的构件依赖管理,故不再重复第三章的内容。
六、IOC容器与设计模式
在正式开始讲解IOC容器之前,笔者首先来问大家一个问题。如何给你的代码解耦?谈到这个话题,相信大家意见会都不一样。但只要尽可能的做到面向接口编程,遵循类型单一设计原则,善用设计模式,这样才能够真正体现出程序的复用性、维护性、扩展性及伸缩性。
既然谈到设计模式,这里不得不提的就是工厂模式系列,该套模式是整个设计模式中最为常用也是最为简单的。不过值得注意的是工厂模式之中并未包含简单工厂模式,从严格意义上来说简单工厂模其实是正统工厂模式的一类变种,但却最为常用和最为人知。
工厂模式可以简单划分为如下3类:
1、简单工厂模式;
2、工厂方法模式;
3、抽象工厂模式;
提示:
为了使大家更好的理解IOC容器的实现,笔者在此有必要为大家逐一讲解各类工厂模式的作用及使用。
那么咱么就先从简单工厂模式谈起吧,不知大家是否都还记得,最初写代码的时候咱们总是喜欢在直接在代码中采用new关键字创建对象实例?虽然这种方式简单直接,但却及其不灵活,代码的耦合度极高,所以我们有必要使用简单工厂模式对其进行解耦操作。简单工厂模式要做的事情就是将业务对象中依赖对象的创建与调用过程进行分离,从而达到明确类的单一原则,这便是简单工厂模式的职责和作用。
简单工厂模式又称之为静态工厂模式,仔细观察如下视图,你将更清晰的明白简单工厂模式的含义:
来看看工厂类的代码:
public class AnimalFactory {
public static Animal createAnimal(String type) throws Exception {
if ("Tiger".equals(type))
return new Tiger();
return new Pig();
}
}
我们既然把对象的创建过程交给了工厂类负责,那么客户端仅需负责调用即可。这样不仅仅可以降低代码耦合,同样也能够更加明确类的单一原则。再来看看客户端的代码:
public class Client {
private static Animal animal;
private static AnimalFactory animalFactory;
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
animal = animalFactory.createAnimal("Tiger");
System.out.println(animal.getType());
}
}
通过上述程序示例,我相信大家已经明白了简单工厂模式的含义。但在这里不得不提的是,虽然简单工厂模式很简单易用,但不知大家有没有想过如果工厂中需要创建的类型不断增多时,我们就要不断的修改这个工厂类,并且我们还需要不停的增加相应的判断逻辑,这样长此以往代码将变得十分冗长,并且对后期的维护是不利的,而且一旦我们使用简单工厂模式,则意味着静态方法将无法被继承重写。
为了解决上述问题,我们再来看看工厂方法模式,该模式有效的解决了简单工厂模式的一些弊端。和简单工厂模式不同的是,工厂方法模式将工厂类设计成了一个抽象的大工厂,由不同的子工厂去实现它,这样便可以清晰且明确的标注每一个子工厂的作用,而且对抽象的工厂类不会有任何影响,后期的代码维护也相对容易些,在简单工厂模式的基础之上增强了程序一定的扩展性和维护性。
工厂方法模式原理图如下所示:
来看看工厂类的代码:
public interface AnimalFactory {
public Animal createAnimal() throws Exception;
}
实现工厂接口的子工厂代码:
public class TigerFactory implements AnimalFactory {
private Tiger tiger;
@Override
public Animal createAnimal() throws Exception {
// TODO Auto-generated method stub
return tiger = new Tiger();
}
}
public class PigFactory implements AnimalFactory {
private Pig pig;
@Override
public Animal createAnimal() throws Exception {
// TODO Auto-generated method stub
return pig = new Pig();
}
}
或许看到这里你会有疑问,如果我要创建100个甚至更多的对象实例时?难道我也要同时创建相等数量的子工厂?那岂不是太麻烦了?基本没有维护性可言。别急抽象工厂模式便能够解决这些问题。
要理解抽象工厂模式,你首要要明确为什么要使用抽象工厂模式,不是越复杂我们就要用,而是为了解决在程序设计过程中一些通用问题的解决方式。抽象工厂模式就是为了解决工厂方法模式的一些弊端,尽可能的减少子工厂数量的编写。
抽象工厂模式的原理其实相对前面2个工厂模式来说,会显得复杂一些。但如果你能够准确的理解如上设计模式的作用,笔者相信抽象工厂模式对于你来说并不是一件难事。先来谈谈抽象工厂模式的概念,所谓抽象工厂模式,原理其实和工厂方法模式雷同,但最主要的是抽象工厂模式并没有打算为每一个对象实例都采用一个专门的工厂类去负责对象的创建工作,因为在很多时候,很多情况下某些对象之间是有关联的,这个时候我们就可以将存在关联的对象放在同一个子工厂里进行创建,这样既可大幅度的减少子工厂的数量,代码也将更易于维护和扩展。
上述笔者为大家详细的讲解完工厂模式后,接下来咱们再来谈谈Spring的IOC容器。IOC(Inversion of Control,控制反转)是整个Spring框架的核心,同样也是工厂模式的一种思想体现。IOC容器主要用于管理对象之间的依赖关系。假设A类与B类建立了关联关系后,当A类需要使用到B类的类成员时,A类则依赖于B类。当B类试图修改自身类成员时必定会影响到A类,那么Sping的任务就是负责管理对象之间的依赖关系,也就是说Spring通过配置文件生成B类的实例,然后在注入到A类中去。然而本来应该由A类来建立的依赖关系,现在却由Spring的Ioc容器来负责管理,这样便有效的降低程序中各个组件之间的耦合度。当然如果想在程序中完全消除耦合度是做不到,且是不实际的,因为面向对象的思想就是基于依赖式设计。
在Spring中IOC容器是基于可插拔式设计,也就是说Ioc容器是通过XML文件进行对象的定义及对象的依赖管理,当对象之间的依赖关系发生改变的时候我们只需修改配置文件即可。当然IOC容器的缺点也很明显,采用反射机制生成对象时,效率明显降低,且相对硬编码的方式来说则显得不直观。
注意:
如果反射机制不是发生在对性能要求特别高的业务中,那便是无关紧要的。
下图为IOC容器的实现原理:
七、依赖注入
其实所谓依赖注入,指的是程序在运行过程中,如果对象之间相互调用协作时,无需在代码中创建被调用者,而是依赖于外部容器进行注入。在这里Spring的IOC容器便充当着这个外部容器。
依赖注入一共有2种方式:
1、投值注入;
2、构造注入;
使用投值注入时,我们只需在Bean类型中定义所关联属性的set方法即可,IOC容器便会根据所定义的set方法动态对其注入。而构造注入则是根据Bean类型中构造函数所定义的参数来进行动态注入。
投值注入Bean代码:
public class LoginBean
{
private String userAccount, passWord;
/* 此处省略set和get方法 */
}
Spring的配置文件中,我们采用投值注入的方式为LoginBean的属性进行外部注入:
<!-- 定义LoginBean实例 -->
<bean id="loginBean" class="org.johngao.bean.LoginBean">
<property name="userAccount" value="JohnGao" />
<property name="passWord" value="123456" />
</bean>
程序运行时,LoginBean中的userAccount何passWord属性将会由Spring的IOC容器来负责具体的初始化工作。和投值注入不同的是,构造注入采用的是构造函数的方式进行参数注入。
构造注入Bean代码:
public class LoginBean
{
public LoginBean(String name)
{
//...
}
}
Spring的配置文件中,我们采用构造注入的方式为LoginBean的参数进行外部注入:
<!-- 定义LoginBean实例 -->
<bean id="loginBean" class="org.johngao.bean.LoginBean">
<!-- 构造注入 -->
<constructor-arg type="java.lang.String" value="JohnGao"/>
</bean>
注意:
笔者在本章中将不会对Spring的配置文件最详细讲解,如果有需要你可以关注笔者该类型博文的连载。由于时间仓促,本文可能存在诸多不尽人意的地方,敬请谅解。