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
,将会显示错误页面,如下图所示: