注:希望大家看后,请给我一点评价,无论写的怎么样,希望你们能给我支持。提出你宝贵的意见。我会继续完善。谢谢您。朋友。
第二章
深入探讨控制反转(
Ioc
)和依赖注入(
DI
)
在第一章中已经对控制反转(Ioc)和依赖注入(DI)有了一个初步的了解,现在让我们通过具体的程序代码来真正的体验一下,它们真的有那么神奇吗?
在演练之前,我还要先讲一下理论知识,然后在演练程序代码。让你也体会到其中的神奇效果。
Spring 中的控制反转(IoC)
(1)IoC = Inversion of Control(由容器控制程序之间的关系)
IoC,用白话来讲,就是由容器来控制程序中的各个类之间的关系,而非传统实现中,直接在代码中由程序代码直接操控。比如在一个类(A)中访问另外一个类中(B)的方法时,我们需要先去new 一个B的对象,然后调用所需的方法。他们的关系很显然在程序代码中控制,同时它们之间的耦合度也比较大,不利于代码的重用。而我们现在把这种控制程序之间的关系交给Ioc容器,让它去帮你实例化你所需要的对象,而你直接在程序中调用就可以了。这也就是所谓"控制反转"的概念的由来:控制权由应用程序的代码中转到了外部容器,控制权的转移,是所谓反转的由来。
可能你不知道Ioc容器到底,或确切的指的是什么?其实这里的容器就相当于一个工厂一样。你需要什么,直接来拿,直接用就可以了,而不需要去了解,去关心你所用的东西是如何制成的,在程序中体现为实现的细节,这里就用到了工厂模式,其实Spring容器就是工厂模式和单例模式所实现的。在第三章中我将会详细介绍Spring的Ioc容器。
对于初学者,我想简单的先说明几点,不要把applicationContext.xml,或带有bean的配置文件理解为容器,它们只是描述了要用到的类(bean)之间的依赖关系。Spring中的容器很抽象,不像Tomcat,Weblogic,WebSphere等那样的应用服务器容器是可见的。Spring的Ioc容器给人的感觉好像就是那些配置文件(applicationContext.xml),我刚开始学时,也以为就是那些带bean的配置文件,虽然它对你学习Spring没什么影响,但如果想更深沉的了解就会迷茫的,我们在这里要正确理解Spring的Ioc容器,以后对我们学习会有很大的帮助的。其实它的容器是有一些类和接口来充当的,你可能又会很迷茫。这就是它与别的框架的不同之处,这一点也正在体现了它的无侵入性的一点,不像EJB需要专门的容器来运行,侵入性很大的重量级的框架。Spring只是一种轻量级的无侵入性的框架。说白了Spring的Ioc容器就是可以实例化BeanFactory或ApplicationContext(扩展了BeanFactory)的类.
可能你对Ioc容器还是不太理解,慢慢来,刚刚接触的人都会很迷茫。我现在通过讲解一个例子来说明它的工作原理,你可能会恍然大悟,原来如此简单。
首先打开你的IDE编译器,我用的是Eclipse3.2+MyEclipse5.5.1
我举了一个大家比较熟悉的例子,用户登录验证。如果用户名为:admin 密码为:1234.
就会在控制台输出”恭喜你,登录成功!”反之输出“对不起,登录失败!”并在日志文件中记录登录信息。这里我用到的是log4j.(日志记录器)。我严格按分层思想和Spring中提倡的按接口编程的思想来演练这个简单的例子。可能你会想这么简单的为什么要那么麻烦呢?怎么不用一个类就解决了,做为程序员,我们时刻要记住,我们的代码要易维护,可重用,易扩展,低耦合,高内聚。如果你了解这些原则,你自然会明白这样麻烦的好处了。
我先整体的讲解一下工程中的目录结果:如下图
首先我们看一下UserLogin接口和它的实现部分UserLoginImpl:
package
com.chap2;
public
interface
UserLogin {
public
boolean
login(String userName,String passWord);
}
package
com.chap2;
@SuppressWarnings
(
"unused"
)
public
class
UserLoginImpl
implements
UserLogin {
public
boolean
login(String userName,String passWord) {
if
(userName.equals(
"admin"
) && passWord.equals(
"1234"
))
return
true
;
else
return
false
;
}
}
上面的接口只是简单的定义了一个方法,用于验证用户身份是否合法。在实现中只是简单的数据比较,实际当中应该从数据库中去取数据进行验证的,我们只要能说明问题就可以了。
然后在看一下业务逻辑层的UserService这里应该再对该类进行提取接口的,我只是写了一个类,如果你有兴趣的话,你可以在加上接口像上面的UserLogin一样,这样做是为了降低代码的耦合度,提高封装性。
package
com.chap2.service;
import
com.chap2.UserLogin;
public
class
UserService {
private
String
userName
;
private
String
passWord
;
private
UserLogin
userLogin
;
public
void
setUserName(String userName) {
this
.
userName
= userName;
}
public
void
setPassWord(String passWord) {
this
.
passWord
= passWord;
}
public
void
setUserLogin(UserLogin userLogin) {
this
.
userLogin
= userLogin;
}
public
String validateUser() {
if
(
userLogin
.login(
userName
,
passWord
))
return
"
恭喜你,登录成功!"
;
else
return
"
对不起,登录失败!"
;
}
}
在这个业务方法里其实是对userLogin中的方法的再次封装,这里面可能隐藏的用到了正面封装的模式(Facade)或叫做门面模式。它的好处是为了不让客户直接访问UserLogin中的方法,在实际的开发中UserLogin的方法应该放在DAO层中,这里的DAO是数据访问层的意思,其实就是对数据库执行的CRUD(增,删,改,查)操作。大家不要因为我多讲了一些就迷。我们应该养成好的编程习惯。也就是分工明细。层与层之间只能通过接口访问。至于上面提到的模式。你可以不先管了,只要你知道它里面用到了就可以了,以后我会把常用到的模式给你讲讲的。
这里的前台客户端的调用,我没用到html,jsp之类的页面,只是用了一个main()方法简单的模拟一下。同样可以起到上面讲的效果的。
public
class
Client {
public
static
void
main(String[] args) {
//
TODO
自动生成方法存根
Log log = LogFactory.getLog(Client.
class
);
//
初始化,并加载配置文件。
ApplicationContext context =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
BeanFactory beanFactory = (BeanFactory) context;
//
得到实例化的UserService
UserService userService = (UserService) beanFactory
.getBean(
"userService"
);
//
输出并记录登录信息
log.info(userService.validateUser());
}
}
先简单的看一下运行的结果是什么。
2007-11-21 11:04:44,765 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c51355: display name [org.springframework.context.support.ClassPathXmlApplicationContext@c51355]; startup date [Wed Nov 21 11:04:44 CST 2007]; root of context hierarchy>
2007-11-21 11:04:44,843 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [applicationContext.xml]>
2007-11-21 11:04:45,031 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@c51355]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1ce2dd4>
2007-11-21 11:04:45,046 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1ce2dd4: defining beans [userLogin,userService]; root of factory hierarchy>
2007-11-21 11:04:45,078 INFO [com.chap2.util.Client] - <
恭喜你,登录成功!>
你可能会想登录用户的信息在那?
所有的配置信息和实例化的工作都交给了Spring的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 [url]http://www.springframework.org/schema/beans/spring-beans-2.0.xsd[/url]"
>
<
bean
id
=
"userLogin"
class
=
"com.chap2.UserLoginImpl"
/>
<
bean
id
=
"userService"
class
=
"com.chap2.service.UserService"
>
<
property
name
=
"userName"
>
<
value
>
admin
</
value
>
</
property
>
<
property
name
=
"passWord"
>
<
value
>
1234
</
value
>
</
property
>
<
property
name
=
"userLogin"
>
<
ref
bean
=
"userLogin"
/>
</
property
>
</
bean
>
</
beans
>
上面的配置文件只是描述了我们所用到类的调用关系,和数据的赋值<初始值>。我们在UserSerivce中要用到UserLogin中的方法,我们只需在这里简单的设置它的一个属性(property)就可以了,以前的编程要在代码中实现了,通过先new 一个UserLogin的实现对象,然后在调用其中的方法。我们这里只需在配置文件中简单的配置一下就可以了,在程序用到这个方法时,容器会自动先实例化UserLoginImpl,然后把它交给UserService使用。在UserService中不用担心实例化,以及管理它的生命周期了。全部让Spring的Ioc容器管理记行了。这样做减少了它们之间的依赖性,也就是降低它们的耦合度。
整个程序的工作流程是这样的。
在main()方法中通过ApplicationContext来加载配置文件,然后把它转换为BeanFactory。这就是我们要讲的Spring中控制反转容器。它把所有的类都初始化,并放在这个容器中。在我们需要的时候只需像 UserService userService = (UserService) beanFactory.getBean("userService");通过配置文件中bean的id或name调用就可以了。不必在像以前的编程那样UserService userSerivce=new UserService();这样做还有一个好处就是让容器来管理它的生命周期,我们只需用就可以了,用完了在交给容器管理。而不用担心什么时候销毁它。
附加一些log4j的文件信息。就是工程目录下的log4j.properties.一定要放到src目录下,要不然程序运行时,它会找不到的。或直接把它放到class目录下。Log4j就是简单的记录一些日志信息,为以后使用的。下面是它的配置。其实还有多中形式的配置,可以是java属性文件形式的。像userLogin.log, userLogin.log.1就是一些备份文件。里面记录了程序运行时的一些信息。
log4j.rootLogger=
INFO,
stdout,logfile
log4j.appender.stdout=
org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=
org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=
%d
%p
[%c]
-
<%m>%n
log4j.appender.logfile=
org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=
userLogin.log
log4j.appender.logfile.MaxFileSize=
2KB
# Keep three backup files.
log4j.appender.logfile.MaxBackupIndex=
3
# Pattern to output: date priority [category] - message
log4j.appender.logfile.layout=
org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=
%d
%p
[%c]
-
%m%n
在这里我就不具体的讲了。在以后的章节中会讲到的。
今天先写到这里.