15.4 Spring控制反转容器的使用
15.4 Spring控制反转容器的使用
本节主要介绍**Spring如何管理bean和依赖关系**。
15.4.1 通过构造器创建一个bean实例
前面已经介绍,通过调用ApplicationContext的getBean方法可以获取到一个bean的实例。下面的配置文件中定义了一个名为product的bean。
一个简单的配置文件
1 | <?xml version="1.0" encoding="UTF-8"?> |
该bean的定义告诉Spring通过默认无参的构造器来初始化Product类。如果不存在该构造器(因为类作者重载了构造器,且没有显式定义默认构造器),则Spring将抛出一个异常。
注意**,应采用id或者name属性标识一个bean**。为了让Spring创建一个Product实例,应将bean定义的name值“product”(具体实践中也可以是id值)和Product类型作为参数传递给ApplicationContext的getBean方法:
1 | ApplicationContext context = |
15.4.2 通过工厂方法创建一个bean实例
除了通过类的构造器方式,**Spring还同样支持通过调用一个工厂的方法来初始化类**。下面的bean定义展示了通过工厂方法来实例化java.util.Calendar:
1 | <!-- 使用id属性来标识一个bean --> |
本例中采用了id属性,而非name属性来标识bean,并采用了getBean方法来获取Calendar实例:
1 | // 加载定义bean的xml文件 |
15.4.3 DestroyMethod的使用
有时,我们希望一些类在被销毁前能执行一些方法。Spring考虑到了这样的需求。可以在bean定义中配置destroy-method属性,来指定在销毁前要被执行的方法。
下面的例子中,我们配置Spring通过java.util.concurrent.Executors的静态方法newCachedThreadPool来创建一个java.uitl.concurrent.ExecutorService实例,并指定了destroy-method属性值为shutdown方法。这样,Spring会在销毁ExecutorService实例前调用其shutdown方法:
1 | <bean id="executorService" class="java.util.concurrent.Executors" |
15.4.4 向构造器传递参数
Spring支持通过带参数的构造器来初始化类。
Product类
1 | package app15a.bean; |
如下定义展示了如何通过参数名传递参数:
1 | <!-- 调用:public Product(String name,String description,float price) --> |
这样,在创建Product实例时,Spring会调用如下构造器:
1 | public Product(String name, String description, float price) { |
除了通过名称传递参数外,Spring还支持通过下标方式传递参数,具体如下:
1 | <bean name="featuredProduct2" class="app15a.bean.Product"> |
需要说明的是,采用这种下标方式,对应构造器的所有参数必须传递,缺一不可。
15.4.5 setter方式依赖注入
下面以Employee类和Address类为例,介绍setter方式依赖注入。
Employee类
1 | package app15a.bean; |
Address类
1 | package app15a.bean; |
Employee依赖于Address类,可以通过如下配置来保证每个Employee实例都能包含Address实例:
1 | <!-- 使用构造器方式注入依赖 --> |
simpleAddress对象是Address类的一个实例,其通过构造器方式实例化。employee1对象则通过配置property元素来调用setter方法以设置值。需要注意的是,**homeAddress属性配置的是simpleAddress对象的引用**。
被引用对象A的配置定义不需要在引用该对象A的对象B之前定义。本例中,employee1对象可以出现在simpleAddress对象定义之前。
15.4.6 构造器方式依赖注入
上面的Employee类提供了一个可以传递参数的构造器,如下所示:
1 | //可以通过构造器注入 |
所以,我们还可以将Address对象通过构造器注入,如下所示:
1 | <!-- 使用构造器方法注入依赖 --> |
完整代码
项目结构
spring-config.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
Main.java
1 | package app15a; |
15.3 XML配置文件
15.3 XML配置文件
从1.0版本开始,Spring就支持基于XML的配置,从2.5版本开始,增加了通过注解的配置支持。
使用XML文件配置beans
下面介绍如何使用XML文件配置beans。beans的xml配置文件的根元素通常为:
1 | <?xml version="1.0" encoding="UTF-8"?> |
如果需要更强的Spring配置能力,可以在schemalocation属性中添加相应的schema。
配置文件可以是一份,也可以分解为多份,以支持模块化配置。ApplicationContext的实现类支持读取多份配置文件。另一种选择是,通过一份主配置文件,将该文件导入到其他配置文件。
beans的xml文件导入其他被子beans的xml文件
下面是一个beans的配置文件导入其他beans的配置文件的示例:
1 | <?xml version="1.0" encoding="UTF-8"?> |
bean元素的配置后面将会详细介绍。
15.2 依赖注入
15.2 依赖注入
在过去数年间,依赖注入技术作为代码可测试性的一个解决方案已经被广泛应用。实际上,Spring、谷歌Guice等伟大框架都采用了依赖注入技术。那么,什么是依赖注入技术?
什么是依赖注入
很多人在使用中并不区分依赖注入和控制反转(IoC),尽管Martin Fowler在其文章中已分析了二者的不同。
http://martinfowler.com/articles/injection.html
实例
简单来说,依赖注入的情况如下。
有两个组件A和B,A依赖于B。假定A是一个类,且A有一个方法importantMethod使用到了B,如下:
1 | public class A { |
要使用**B类,A类必须先获得组件B的实例的引用**。若B是一个具体类,则可通过new关键字直接创建组件B实例。但是,如果B是接口,且有多个实现,则问题就变得复杂了。我们固然可以任意选择接口B的一个实现类,但这也意味着A的可重用性大大降低了,因为无法采用B的其他实现。
依赖注入是这样处理此类情景的:接管对象的创建工作,并将该对象的引用注入需要该对象的组件。
以上述例子为例,依赖注入框架会分别创建对象A和对象B,将对象B注入到对象A中。
依赖注入方式
**为了能让框架进行依赖注入,程序员需要编写特定的set方法或者构造方法**。
setter方法注入
例如,为了能将B注入到A中,类A会被修改成如下形式:
1 | public class A { |
修改后的类A新增了一个setter方法,该setter方法将会被框架调用,用以注入一个B的实例。由于对象依赖由依赖注入,类A的importantMethod方法不再需要在调用B的usefulMethod方法前去创建一个B的实例。
构造器注入
当然,也可以采用构造器方式注入,如下所示:
1 | public class A { |
本例中,**Spring会先创建B的实例,再创建实例A,然后把B注入到实例A中。
注意Spring管理的对象称为beans。
通过提供一个控制反转容器**(或者依赖注入容器),Spring为我们提供一种可以“聪明”地管理Java对象依赖关系的方法。其优雅之处在于,程序员无须了解Spring框架的存在,更不需要引入任何Spring类型。
版本支持
- 从1.0版本开始,**
Spring就同时支持setter和构造器方式的依赖注入**。 - 从2.5版本开始,通过
Autowired注解,Spring支持基于field方式的依赖注入,但缺点是程序必须引入org.springframework.beans.factory.annotation.Autowired,这对Spring产生了依赖,这样,程序无法直接迁移到另一个依赖注入容器内。
使用Spring,程序几乎将所有重要对象的创建工作移交给Spring,并配置如何注入依赖。
Spring支持XML和注解两种配置方式。此外,还需要创建一个ApplicationContext对象,ApplicationContext对象代表一个Spring控制反转容器,
ApplicationContext接口
org.springframework.context.ApplicationContext接口有多个实现,包括ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。这两个实现都需要至少一个包含beans信息的XML文件。ClassPathXmlApplicationContext尝试在类加载路径中加载配置文件,而FileSystemXmlApplicationContext则从文件系统中加载。
下面为从类路径中加载config1.xml和config2.xml的ApplicationContext创建的一个代码示例:
1 | ApplicationContext context = |
可以通过调用ApplicationContext的getBean方法获得对象:
1 | Product product = context.getBean("product", Product.class); |
getBean方法会查询id为product且类型为Product的bean对象。
注
理想情况下,我们仅需在测试代码中创建一个ApplicationContext,应用程序本身无须处理。对于Spring MVC应用,可以通过一个Spring Servlet来处理ApplicationContext,而无须直接处理。
15.1 Spring入门
15.1 Spring入门
Spring模块都打包成JAR文件,其命名格式如下:
1 | spring-maluleName-x.y.z.RELEASE.jar |
其中module name是模块的名字,而x.y.z是spring的版本号。例如:Spring的4.1.12版本中的beans模块的包全名为:spring-beans-4.1.12.RELEASE.jar。
推荐采用Maven或Gradle工具来下载Spring模块,具体操作步骤可以参见Spring官网:
http://projects.spring.io/spring-framework
采用类似Maven以及Gradle这样的工具有一个好处,即下载一个Spring模块时会自动下载其所依赖的模块。
如果不熟悉以上两种工具,则可以通过如下链接下载包括所有模块的压缩文件:
http://repo.spring.io/release/org/springframework/spring/
Spring MVC maven依赖
以下是Spring MVC用到的依赖,把下面的代码复制到pom.xml的<dependencies></dependencies>标签下即可.
1 | <!-- Spring MVC依赖 --> |
第15章 Spring框架
12.3.5 SSL是怎么工作的
12.3.5 SSL是怎么工作的
HTTPS协议
从浏览器地址栏中可看到协议:例如https://www.amazon.cn/,有的浏览器也会在地址栏显示一个安全的图标(一个绿色的锁),如下图所示:
HTTP协议是明文传输的,并且不能验证对方的身份,而且不能保证数据的完整性。而当我们在网络上进行购物电子交易时,电子网银转账时,这种方式就显得很不安全了。如果黑客截取了我们和服务器端的通信数据,那么黑客就能获取我们的一些敏感信息了。所以,HTTPS应运而生!HTTPS是在HTTP协议的基础上加入了SSL协议,**SSL协议加在了传输层和应用层之间**,如下图所示:
在加入了SSL协议后,HTTPS相比HTTP更加的安全,其对数据的传输进行了加密处理,并且能验证通信双方的身份,还保证了数据传输过程的完整性。这样,即使黑客截取了我们和服务器之间的通信数据,它也获取不到任何有用的信息。
需要说明的是,**SSL协议是独立于HTTP的协议**,所以不光是HTTP协议,其他运行在应用层的SMTP和Telnet等协议均可配合SSL协议使用。可以说SSL是当今世界上应用最为广泛的网络安全技术。
申请SSL数字证书
SSL证书是需要向第三方CA机构申请的,现在大部分的SSL证书都是要付费申请的。
申请SSL证书流程
我们拿amazon.com为例:
- 亚马逊首先生成一对公私钥,私钥自己保存。公钥用于申请
SSL证书 - 然后将公钥和
amazon.com的一些身份信息发送给第三方可信任CA机构. CA机构收到亚马逊的申请之后,将执行一些必要的步骤,以确信请求确实由亚马逊发送而来,并且这些信息是正确的。- 然后,认证中心亚马逊发来的公钥和亚马逊的身份信息进行数字签名,生成数字证书,然后发送给亚马逊。
- 数字证书里面包含了亚马逊的公钥、亚马逊的**身份信息 **和 第三方
CA机构的数字签名。
SSL协议工作过程
当买家进入一个亚马逊的网站时,他的浏览器和亚马逊的服务器在后台发生了如下事件:
- 浏览器访问 https://www.amazon.cn/
- 服务器将网站的证书信息发送给客户端
- 浏览器通过证书上第三方可信任机构CA的签名,向该机构验证证书的真假
- 如果该证书是真的,
- 要求服务器证明是该证书的拥有者
- 如果该证书是真的,
- 服务器向浏览器发送消息和使用服务器私钥加密的消息的摘要
- 浏览器使用证书中服务器的公钥解密消息的摘要,
- 浏览器生成另一份消息的摘要,比较两个摘要,
- 如果两个摘要相同,则说明该服务器确实是该证书的拥有者,
- 则浏览器生成会话秘钥对,把会话公钥作为消息,然后使用服务器公钥加密该消息,然后发送给服务器。
- 如果两个摘要相同,则说明该服务器确实是该证书的拥有者,
- 浏览器生成另一份消息的摘要,比较两个摘要,
- 服务器使用服务器的私钥解密该消息,就得到了浏览器会话的公钥
- 最后,浏览器和服务器双方都拥有对方的公钥,相互通信时,只需要:
- 使用对方的公钥加密要向对方发送的消息,
- 使用自己的私钥解密从对方接收到的消息。
过程如下图所示:
参考资料
12.3 安全套接层 12.3.1密码学
12.3 安全套接层
Secure Socket Layer,是Netscape研发的用以保障在Internet上安全传输数据的技术,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取及窃听。充分理解SSL是如何工作的,有很多你需要学习技术,从加密到私钥和公钥对,再到证书。本节讨论详细SSL及其组件。
12.3.1密码学
我们时不时需要一个安全信息通道,使得信息是安全的,即便外部可以访问信息,也不能篡改信息。
从历史上看,密码只关心加密和解密,在双方可以放心的交换信息,只有他们可以读取消息。在开始的时候,人们使用对称密码加密和解密消息。**对称密码使用相同的密钥来加密和解密消息**。这是一个非常简单的加密/解密技术。
对称加密例子
假设,加密方法是:循环前移字母表中每个字符n位数。
- 因此,如果密码是
2,加密版“ThisFriday”是“vjkuhtkfca”。当你到了字母表结尾的单词,循环前移两位将得到字母表开头的单词,因此y变成a。 - 相应的,如果知道密钥是
2,则对密文循环右移2位即可解密消息。
对称加密不适合互联网
对称加密要求双方提前知道用于加密/解密的密钥。对称加密是不适合互联网的原因如下:
- 两人交换消息往往不知道对方。例如,在亚马逊网站上购买一本书,你需要发送您的个人资料和信用卡信息。如果对称密码被使用,你必须调用亚马逊的交易之前必须同意这个密钥。
- 每个人都希望能够与其他各方沟通。如果使用对称密码,每个人在不同的地方都会有不同的独特的钥匙。
- 信息在互联网上通过许多不同的计算机传播。这样很容易挖掘其他人的消息。对称密码体制并不能保证数据不被第三方篡改。
因此,今天的安全通信在互联网上使用非对称加密,提供了这三个特点:
- 加密/解密。信息对第三方进行加密隐藏。只有预期的接收者才能解密。
- 身份验证。验证确保实体就是声称者。
- 数据的完整性。许多计算机在互联网上发送的消息传递。它必须是确保发送的数据不变,完好无损。
公钥私钥
在非对称加密中,加密和解密的数据是通过使用一对非对称密钥(公钥和私钥)来实现的。
- 私钥是私有的。颁发者必须保持私钥放在一个安全的地方,不能落入任何另一方的手里。
- 公钥用于加密,通常谁都可以下载公钥与颁发者进行沟通。
您可以使用工具来生成公钥和私钥。这些工具将在本章后面讨论。
公钥加密的优点
公钥加密的优点是:使用公共密钥加密的数据只能使用对应的私钥进行解密,同样的,使用私钥加密的数据只能使用对应的公钥解密。
这优雅的**算法是基于大素数**,由Ron Rivest,Adi Shamir,和Len Adleman在麻省理工学院(MIT)在1977年发明的。它们简称为RSA算法,基于他们的姓氏的首字母。
RSA算法是在互联网上的被广泛使用,特别是电子商务领域。
爱丽丝(Alice)与鲍伯(Bob)是广泛地代入密码学和物理学领域的通用角色。这里我也会使用他们。
12.2.1 基于表单的认证
12.2.1 基于表单的认证
基本访问认证和摘要访问认证不允许你使用一个定制的登录表单。如果你必须有一个自定义窗体,那么你可以使用基于表单的认证。由于基于表单的认证会发送明文,你应当与SSL配合使用。
基于表单的身份认证要求
基于表单的身份验证,您需要创建一个登录页面和一个错误的页面,这可以是HTML或JSP页面。
基于表单的认证流程
第一次请求受保护的资源,servlet和JSP容器将显示登录页面。
- 在成功登录时,所请求的资源将被发送。
- 如果登录失败,用户会看到错误页。
部署描述符要求
使用form-based authentication(基于表单的身份验证),您的部署描述符的auth-method 元素的值必须是FORM(大写)。此外,login-config元素必须有form-login-config元素节点,该节点有两个子元素,form-login-page和form-error-page。如下是一个基于表单的身份验证登录 login-config元素的示例:
1 | <login-config> |
实例
app12d的部署描述符,基于表单身份验证的例子。
项目结构

部署描述符web.xml
1 | <?xml version="1.0" encoding="ISO-8859-1"?> |
form-login-page元素使用下面的login.html页面,formerror-page元素使用下面的Error.html。
login.html
1 | <!DOCTYPE HTML> |
error.html
1 | <!DOCTYPE HTML> |
Servlet1.java
1 | package servlet; |
1.jsp
1 | <!DOCTYPE HTML> |
运行效果
测序下app12d基于表单的认证,直接浏览器这个URL。
http://localhost:8080/app12d/servlet1login.html的用户登录页面如图下图所示:
输入正确的用户名tom,密码tom,将会显示所请求的servlet1页面,如下图所示:
关闭浏览器,重新访问上述链接,输入错误的用户名123,密码123,将会显示错误页面,如下图所示:

