数组可以装基本类型或者引用,collections只能装引用。
通常有两种方法可以扩展collection 来满足一些需要:继承某种集合类型和封装某种集合类型。第一种的优点是初始化的时候在内存中只产生一个对象,这是继承特性决定的。后者的优点是我们可以方便控制被封装集合的各种属性。
Whenever possible, it’s desirable to bury implementation details inside of a class rather than exposing client code to such details。例:
法1:
public class Student {
private String name;
private String studentId;
private ArrayList<TranscriptEntry> transcript; //成绩报告单
public void addTranscriptEntry(TranscriptEntry te) { // 操作transcript达到记录成绩
// Store the TranscriptEntry in our ArrayList.
transcript.add(te);
}
}
客户端调用代码:
Student s = new Student("1234567", "James Huddleston");
Course c = new Course("LANG 800", "Advanced Language Studies");
TranscriptEntry te = new TranscriptEntry(c, "Fall 2006", "B+");
s.addTranscriptEntry(te);
法2:
建立新对象,封装一个ArrayList:
public class Transcript {
private ArrayList<TranscriptEntry> transcriptEntries;
public void courseCompleted(Course c, String semester, String grade) {
// Instantiate and insert a brand-new TranscriptEntry object into the
// ArrayList - details hidden away!
transcriptEntries.add(new TranscriptEntry(c, semester, grade);
}
}
public class Student {
private String name;
private String studentId;
// This used to be declared as an ArrayList.
private Transcript transcript;
// etc.
}
客户端代码:
s.courseCompleted(c, "Spring 2006", "A+");
第二种方法使Student处理更少的细节,不用管transcripts怎么表达,看不到TranscriptEntry的存在。客户端代码更简单。
Aggregation is a special form of association, alternatively referred to as the “consists of”, “is composed of”, or “has a” relationship.Like an association, an aggregation is used to represent a relationship between two classes, A and B. But, with an aggregation, we’re representing more than mere relationship: we’re stating that an object belonging to class A, known as an
aggregate,is composed of, or contains,
component objects belonging to class B.
Note that these aggregation statements appear very similar to associations, where the name of the association just so happens to be is composed of or contains. That’s because an aggregation is an association in the broad sense of the term(aggregation 是association的一种特殊表现形式)!aggregation 和associations UML表现不同但最终代码表现形式一样
Composition is a strong form of aggregation, in which the “parts” cannot exist without the “whole.” As an example, given the relationship “a Book is composed of many Chapters”, we could argue that a chapter cannot exist if the book to which it belongs ceases to exist; whereas given the relationship “a Car is composed of many Wheels”, we know that a wheel can be removed from a car and still serve a useful purpose. Thus, we’d categorize the Book–Chapter relationship as composition and the Car–Wheel relationship as aggregation.
继承没留意的好处:
•
Best of all, we can derive a new class from an existing class even if we don’t own the source code for the latter! As long as we have the compiled bytecode version of a class, the inheritance mechanism works just fine; we don’t need the original source code of a class in order to extend it.
This is one of the most dramatic ways to achieve productivity with an objectoriented language: find a class (either one written by someone else or one that is built into the language) that does much of what you need, and create a subclass of that class,adding just those features that you need for your own purposes.
•
classification is the natural way that humans organize information; so, it only makes sense that we’d organize software along the same lines, making it much more intuitive and hence easier to develop, maintain,extend, and communicate with users about.
继承与Association, aggregation异同(P186):
Association, aggregation, and inheritance are all said to be relationships between classes. Where inheritance differs from association and aggregation is at the
object level.inheritance is indeed a relationship between
classes, but
not between distinct
objects.
注意:避免连锁反应,
Whenever possible, avoid adding features to non-leaf classes once they have been established in code form in an application, to avoid ripple effects throughout an inheritance hierarchy. 说比做容易,这就要求在编码之前尽可多的花时间在需求分析和对象建模阶段,虽然不能避免新需求出现,但至少避免忽视遗漏了当前的需求。
Overriding:子类继承父类,重写唯一能改变的是方法的访问控制,而且只能比父类更宽松,如父类用的是private,子类可以用public。参考了下thinking in java 4th P202 发现这种说法不对,而且是一个
陷阱:父类的该方法根本对子类不可见!子类的该方法实际上是一个全新的方法,连重载都算不上。所以只能重写non-private方法。遇到private方法你得小心,没有编译错误,但不会像你想象的工作,最好给方法重新取名,避免陷阱。
不要做的事情:
We shouldn’t change the semantics—that is, the intention, or meaning—of a feature.For example:
• If the print method of a superclass such as Student is intended to display the values of all of an object’s attributes in the command window, then the print method of a subclass such as GraduateStudent shouldn’t, for example, be overridden so that it directs all of its output to a file instead.
• If the name attribute of a superclass such as Person is intended to store a person’s name in “last name, first name” order, then the name attribute of a subclass such as Student should be used in the same fashion.
We can’t physically eliminate features, nor should we effectively eliminate them by ignoring them. To attempt to do so would break the spirit of the “is a” hierarchy. By definition, inheritance requires that all features of all ancestors of a class A must also apply to class A itself in order for A to truly be a proper subclass. If a GraduateStudent could eliminate the degreeSought attribute that it inherits from Student, for example, is a GraduateStudent really a Student after all? Strictly speaking, the answer is no.
进一步从实用角度说,如果我们重写一个方法但不在这方法里做任何事情,其他继承我们类的人就会出问题:他们觉得我们的方法是有意义的(特别是他们不能看到我们源代码的时候)。而我们则打破了“is a” 原则,所以绝不要这样做!
protected关键字的运用,用于控制继承的访问控制。
运用super(arguments) 减少子类构造函数重复父类构造函数代码,和this类似必须在构造函数最开始调用。
Student s = new Student("Fred", "123-45-6789"); 执行这段代码,Object的构造函数会自动执行,接着Student 的父类Person构造函数仔细,然后是我们调用的Student构造函数,如果调用的Student构造函数没有显示调用父类构造函数,则相当于默认调用super() 。
java没有类的多继承,多继承很复杂的一点,如果两个父类都有相同的方法,子类怎么处理?
We may wish to instantiate additional objects related to the Student object:
初始化与对象相关的一些额外对象:
public class Student() {
// Every Student maintains a handle on his/her own individual Transcript object.
private Transcript transcript;
public Student() {
// Create a new Transcript object for this new Student.
transcript = new Transcript();
// etc.
}
}
读取数据库来初始化对象属性:
public class Student {
// Attributes.
String studentId;
String name;
double gpa;
// etc.
// Constructor.
public Student(String id) {
studentId = id;
// Pseudocode.
use studentId as a primary key to retrieve data from the Student table of a
relational database;
if (studentId found in Student table) {
retrieve all data in the Student record;
name = name retrieved from database;
gpa = value retrieved from database;
// etc.
}
}
// etc.
}
和其他已存在的对象交流:
public class Student {
// Details omitted.
// Constructor.
public Student(String major) {
// Alert the student's designated major department that a new student has
// joined the university.
// Pseudocode.
majorDept.notify(about this student ...);
// etc.
}
// etc.
}
好习惯:如果需要有参数的构造函数,最好同时显示声明一个无参构造函数。
容易出现的bug:如果给构造函数加上void编译会通过!不过会被当作方法而不是构造函数!
当有多个构造函数,而且都有共同的初始化内容时,就会出现很多重复的代码,比如构造一个新学生,我们会做:
1 通知登记办公室学生的存在
2 给学生创建学生成绩报告单
重复引起以后修改必须修改多处,如果使用this 会得到改善
public class Student {
// Attribute details omitted.
// Constructor #1.
public Student() {
// Assign default values to selected attributes ... details omitted.
// Do the things common to all three constructors in this first
// constructor ...
// Pseudocode.
alert the registrar's office of this student's existence
// Create a transcript for this student.
transcript = new Transcript();
}
// Constructor #2.
public Student(String s) {
// ... then, REUSE the code of the first constructor within the second!
this();
// Then, do whatever else extra is necessary for constructor #2.
this.setSsn(s);
}
// Constructor #3.
public Student(String s, String n, int i) {
// ... and REUSE the code of the first constructor within the third!
this();
// Then, do whatever else extra is necessary for constructor #3.
this.setSsn(s);
this.setName(n);
this.setAge(i);
}
// etc.
}
注意:this必须在方法最前面调用
应用一:解决tomcat下中文乱码问题(先来个简单的)
在tomcat下,我们通常这样来解决中文乱码问题:
过滤器代码:
- package filter;
-
- import java.io.*;
- import javax.servlet.*;
- import javax.servlet.http.*;
- import wrapper.GetHttpServletRequestWrapper;
-
- public class ContentTypeFilter implements Filter {
-
- private String charset = "UTF-8";
- private FilterConfig config;
-
- public void destroy() {
- System.out.println(config.getFilterName()+"被销毁");
- charset = null;
- config = null;
- }
-
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
-
- request.setCharacterEncoding(charset);
- response.setCharacterEncoding(charset);
-
- HttpServletRequest req = (HttpServletRequest)request;
-
-
- System.out.println("----请求被"+config.getFilterName()+"过滤");
-
- chain.doFilter(req, response);
-
- System.out.println("----响应被"+config.getFilterName()+"过滤");
-
- }
-
- public void init(FilterConfig config) throws ServletException {
- this.config = config;
- String charset = config.getServletContext().getInitParameter("charset");
- if( charset != null && charset.trim().length() != 0)
- {
- this.charset = charset;
- }
- }
-
- }
web.xml中过滤器配置
<!--将采用的字符编码配置成应用初始化参数而不是过滤器私有的初始化参数是因为在JSP和其他地方也可能需要使用-->
<context-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</context-param>
<filter>
<filter-name>ContentTypeFilter</filter-name>
<filter-class>filter.ContentTypeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ContentTypeFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
equest.setCharacterEncoding(charset); 必须写在第一次使用request.getParameter()之前,这样才能保证参数是按照已经设置的字符编码来获取。
response.setCharacterEncoding(charset);必须写在PrintWriter out = request.getWriter()之前,这样才能保证out按照已经设置的字符编码来进行字符输出。
通过过滤器,我们可以保证在Servlet或JSP执行之前就设置好了请求和响应的字符编码。
但是这样并不能完全解决中文乱码问题:
对于post请求,无论是“获取参数环节”还是“输出环节"都是没问题的;
对于get请求,"输出环节"没有问题,但是"获取参数环节"依然出现中文乱码,所以在输出时直接将乱码输出了。
原因是post请求和get请求存放参数位置是不同的:
post方式参数存放在请求数据包的消息体中。 get方式参数存放在请求数据包的请求行的URI字段中,以?开始以param=value¶me2=value2的形式附加在URI字段之后。而request.setCharacterEncoding(charset); 只对消息体中的数据起作用,对于URI字段中的参数不起作用,我们通常通过下面的代码来完成编码转换:
String paramValue = request.getParameter("paramName");
paramValue = new String(paramValue.trim().getBytes("ISO-8859-1"), charset);
但是每次进行这样的转换实在是很麻烦,有没有统一的解决方案呢?
解决方案1: 在tomcat_home"conf"server.xml 中的Connector元素中设置URIEncoding属性为合适的字符编码
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"
/>
这样做的缺点是,同一个tomcat下的其他应用也将受到影响。而其每次部署时都需要类修改配置也很麻烦。
解决方案2:自定义请求包装器包装请求,将字符编码转换的工作添加到getParameter()方法中
package wrapper;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper {
private String charset = "UTF-8";
public GetHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
/** *//**
* 获得被装饰对象的引用和采用的字符编码
*
* @param request
* @param charset
*/
public GetHttpServletRequestWrapper(HttpServletRequest request,
String charset) {
super(request);
this.charset = charset;
}
/** *//**
* 实际上就是调用被包装的请求对象的getParameter方法获得参数,然后再进行编码转换
*/
public String getParameter(String name) {
String value = super.getParameter(name);
value = value == null ? null : convert(value);
return value;
}
public String convert(String target) {
System.out.println("编码转换之前:" + target);
try {
return new String(target.trim().getBytes("ISO-8859-1"), charset);
} catch (UnsupportedEncodingException e) {
return target;
}
}
}
修改过滤器的doFilter方法 代码如下:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 设置请求响应字符编码
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
// 新增加的代码
HttpServletRequest req = (HttpServletRequest) request;
if (req.getMethod().equalsIgnoreCase("get")) {
req = new GetHttpServletRequestWrapper(req, charset);
}
System.out.println("----请求被" + config.getFilterName() + "过滤");
// 传递给目标servlet或jsp的实际上时包装器对象的引用,而不是原始的HttpServletRequest对象
chain.doFilter(req, response);
System.out.println("----响应被" + config.getFilterName() + "过滤");
}
这样一来,在servlet中调用包装器的getParameters方法来获取参数,就已经完成了字符编码的转换过程,我们就不需要在每次获取参数时来进行字符编码转换了。
总结:自己写类继承HttpServletRequestWrapper,HttpServletRequestWrapper实现了HttpServletRequest接口。看tomcat的源代码可以发现,ServletRequest作为一个Component ,ServletRequestWrapper作为一个比较标准的Decorator ,实现ServletRequest接口并把ServletRequest当作成员变量,其他继承Decorator 的类(比如本例中的GetHttpServletRequestWrapper )就可以很好的操控ServletRequest及其子类(比如本例中的HttpServletRequest),HttpServletRequest的很多方法就可以根据我们的需求做改变,比如设置字符,去掉空格。
参考:
http://www.javaeye.com/topic/483158
http://fishhappy365.javaeye.com/blog/484185
http://www.javaeye.com/topic/220230
- HTTP 定义了与服务器交互的不同方法,最基本的方法是 GET 和 POST。事实上 GET 适用于多数请求,而保留 POST 仅用于更新站点。根据 HTTP 规范,GET 用于信息获取,而且应该是 安全的和 幂等的。所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。幂等的意味着对同一 URL 的多个请求应该返回同样的结果。完整的定义并不像看起来那样严格。从根本上讲,其目标是当用户打开一个链接时,她可以确信从自身的角度来看没有改变资源。比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。反之亦然。POST 请求就不那么轻松了。POST 表示可能改变服务器上的资源的请求。仍然以新闻站点为例,读者对文章的注解应该通过 POST 请求实现,因为在注解提交之后站点已经不同了(比方说文章下面出现一条注解);
- 在FORM提交的时候,如果不指定Method,则默认为GET请求,Form中提交的数据将会附加在url之后,以?分开与url分开。字母数字字符原样发送,但空格转换为“+“号,其它符号转换为%XX,其中XX为该符号以16进制表示的ASCII(或ISO Latin-1)值。GET请求请提交的数据放置在HTTP请求协议头中,而POST提交的数据则放在实体数据中;
- GET方式提交的数据最多只能有1024字节,而POST则没有此限制。
Web 上最常用的两种 Http 请求就是 Get 请求和 Post 请求了。我们在做 java web 开发时,也总会在 servlet 中通过 doGet 和 doPost 方法来处理请求;更经常地,我们会在 doGet 方法的实现中调用 doPost 方法。尽管做了近两年的 web 开发,我对诸如 Get 请求和 Post 请求的基本概念仍不是十分了解。近日阅读《 javascript 高级程序设计》重新整理了一下 Get 请求和 Post 请求的概念,算是读书笔记吧。
Get 是从服务器上获取数据,这是最常见的请求类型。每次在浏览器中输入 URL 打开页面时,就是向服务器发送一个 Get 请求。 Get 请求的参数是用问号追加到 URL 结尾,后面跟着用&连接起来的名称/值对。比如网址 http://bt.neupioneer.com/viewthread.php?tid=87813 ,其中 tid 为参数名, 87813 为参数的值。在编程中,使用 Get 最多的地方就是超链接列表,其中的参数多是从数据库读出的字段拼接而成。在 Ajax 中,我们也经常使用 Get ,通过提取出页面的标签值,拼成串后构造一个 URL 。 Get 在使用上是有限制的, URL 的最大长度为 2KB ,因此,如果表单中包含textarea这样的大文本段,就不要用Get了。对于表单来说, Get 是把参数数据队列加到提交表单的 ACTION 属性所指的 URL 中,值和表单内各个字段一一对应,通过 URL 可以看到中传递的参数。因此,相比于 Post ,它是不安全的。
Post 的使用场合多是在表单提交的地方,因为和 Get 相比, Post 可以发送更多的数据,《 javascript 高级程序设计》中说最多可以发送 2GB ,这多少让我不太相信,网上一些文章说 IIS4 中最大量为 80KB , IIS5 中为 100KB ,不知道 Tomcat 中的情况如何。 Post 是通过 HTTP Post 机制,将表单内各个字段与其内容放置在 HTML Header 内一起传送到 ACTION 属性所指的 URL 地址。 和 Get 相比, Post 的内容是不会在 URL 中显现出来的,这多少是安全一些的。 我在做登录这样的表单时,只是将请求方式设为 Post ,使得用户名和密码信息不在浏览器中显现,但不清楚的是,是否有更好的方法加密密码等信息(实在不知道如果请求不传到服务器的话,怎么对未知的请求加密,清楚的朋友不妨给个解决方案)。在 Ajax 中,如果要和服务器交互,记得加上 request.setRequestHeader(“Content-Type”,”application/x-www-urlencoded”); 这一脚本,尽管很多 Ajax 教材中都提到了这一点。另外要说的是,被传递的参数是要经过编码的。在 javascript 中,编码函数是 encodeURIComponent(xx) 。
以上转自 http://blog.csdn.net/yoland/archive/2009/03/19/4005740.aspx
一个get的发送请求例子:
GET /select/selectBeerTaste.jsp?color=dark&taste=malty HTTP/1.1
Host: www.wickedlysmart.com
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/
20030624 Netscape/7.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/
plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
一个post的发送请求例子:
POST /advisor/selectBeerTaste.do HTTP/1.1
Host: www.wickedlysmart.com
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/
20030624 Netscape/7.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/
plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
color=dark&taste=malty (Request headers 后面有一个传参数的body)
两个例子的红色的部分很好的看出了get和post传参数区别
上面是CAS 基础协议图
CAS 介绍
CAS 具有以下特点:
-
开源的企业级单点登录解决方案。
-
CAS Server 为需要独立部署的 Web 应用。
-
CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。
CAS 原理和协议
从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。
CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web
请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service
Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service
(也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server
随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service
所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service
和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份合适,以确保 Service Ticket 的合法性。
在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。
另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。
在 Tomcat 上部署一个完整的 CAS Server 主要按照以下几个步骤:
配置 Tomcat 使用 Https 协议
如果希望 Tomcat 支持 Https,主要的工作是配置 SSL 协议,其配置过程和配置方法可以参考 Tomcat 的相关文档。不过在生成证书的过程中,会有需要用到主机名的地方,CAS 建议不要使用 IP 地址,而要使用机器名或域名。(项目过程中没使用)
部署 CAS Server
CAS
Server 是一个 Web 应用包,将前面下载的 cas-server-3.1.1-release.zip 解开,把其中的
cas-server-webapp-3.1.1.war 拷贝到 tomcat的 webapps 目录,并更名为
cas.war。由于前面已配置好 tomcat 的 https 协议,可以重新启动
tomcat,然后访问:https://localhost:8443/cas(没配置ssh应该是http://localhost:8080/cas) ,如果能出现正常的 CAS 登录页面,则说明 CAS
Server 已经部署成功。
虽然 CAS Server 已经部署成功,但这只是一个缺省的实现,在实际使用的时候,还需要根据实际概况做扩展和定制,最主要的是扩展认证 (Authentication) 接口和 CAS Server 的界面。
参考http://www.ibm.com/developerworks/cn/opensource/os-cn-cas/index.html
项目也采用jdbc验证,文章介绍了三个基于 JDBC 的 AuthenticationHandler,我使用的cas-server 3.3 多了一个AbstractJdbcUsernamePasswordAuthenticationHandler 直接使用。
部署客户端应用
单点登录的目的是为了让多个相关联的应用使用相同的登录过程,使用的是cas-client-core- 3.1.9.jar 放入要使用单点登录项目的lib下。cas客户端使用的是过滤器,在web.xml配置。可以使用spring配置bean。官方网站http://www.ja-sig.org/wiki/display/CASC/Configuring+the+JA- SIG+CAS+Client+for+Java+using+Spring 有配置。
用类扩展 Cas20ProxyReceivingTicketValidationFilter复写onSuccessfulValidation方法,写入验证成功后做的事情,比如把用户信息放入session。写了一个a.jsp用于跳转,过滤器针对他,访问这个页面的时候,会到cas服务器端,如果成功的话,返回到a.jsp,这个页面用sendRedirect调用了一个action方法处理登录成功后直接进入项目首页。
1 . SSL(Server Socket Layer) 简介
在网络上信息在源 -
宿的传递过程中会经过其它的计算机。一般情况下,中间的计算机不会监听路过的信息。但在使用网上银行或者进行信用卡交易的时候有可能被监视,从而导致个人
隐私的泄露。由于 Internet 和 Intranet
体系结构的原因,总有某些人能够读取并替换用户发出的信息。随着网上支付的不断发展,人们对信息安全的要求越来越高。因此 Netscape
公司提出了 SSL 协议,旨在达到在开放网络 (Internet) 上安全保密地传输信息的目的,这种协议在 WEB 上获得了广泛的应用。 之后
IETF(ietf.org) 对 SSL 作了标准化,即 RFC2246 ,并将其称为 TLS ( Transport Layer
Security ),从技术上讲, TLS1.0 与 SSL3.0 的差别非常微小。
2 . SSL 工作原理
SSL 协议使用不对称加密技术实现会话双方之间信息的安全传递。可以实现信息传递的保密性、完整性,并且会话双方能鉴别对方身份。不同于常用的 http 协议,我们在与网站建立 SSL 安全连接时使用 https 协议,即采用 https://ip:port/ 的方式来访问。当我们与一个网站建立 https 连接时,我们的浏览器与 Web Server 之间要经过一个握手的过程来完成身份鉴定与密钥交换,从而建立安全连接。具体过程如下:
用户浏览器将其 SSL 版本号、加密设置参数、与 session 有关的数据以及其它一些必要信息发送到服务器。
服务器将其 SSL 版本号、加密设置参数、与 session 有关的数据以及其它一些必要信息发送给浏览器,同时发给浏览器的还有服务器的证书。如果配置服务器的 SSL 需要验证用户身份,还要发出请求要求浏览器提供用户证书。
客户端检查服务器证书,如果检查失败,提示不能建立 SSL 连接。如果成功,那么继续。客户端浏览器为本次会话生成 pre-master
secret
,并将其用服务器公钥加密后发送给服务器。如果服务器要求鉴别客户身份,客户端还要再对另外一些数据签名后并将其与客户端证书一起发送给服务器。
如果服务器要求鉴别客户身份,则检查签署客户证书的 CA 是否可信。如果不在信任列表中,结束本次会话。如果检查通过,服务器用自己的私钥解密收到的 pre-master secret ,并用它通过某些算法生成本次会话的 master secret 。
客户端与服务器均使用此 master secret 生成本次会话的会话密钥 ( 对称密钥 ) 。在双方 SSL 握手结束后传递任何消息均使用此会话密钥。这样做的主要原因是对称加密比非对称加密的运算量低一个数量级以上,能够显著提高双方会话时的运算速度。
客户端通知服务器此后发送的消息都使用这个会话密钥进行加密。并通知服务器客户端已经完成本次 SSL 握手。
服务器通知客户端此后发送的消息都使用这个会话密钥进行加密。并通知客户端服务器已经完成本次 SSL 握手。
本次握手过程结束,会话已经建立。双方使用同一个会话密钥分别对发送以及接受的信息进行加、解密。
为了支持请求代理,Servlet API 2.1 引入
javax.servlet.RequestDispatcher接口。servlet调用请求对象的getRequestDispatcher()的方法获得RequestDispatcher实例,此实例可以dispatch到指定的路径上的组件(如servlet, JSP,静态文件等):
public RequestDispatcher ServletRequest.getRequestDispatcher(String path) ,可以是相对路径但不能超过当前的servlet context,不过可以使用getContext方法转发到当前context之外的地方,只是没有任何方法转发到另一个服务器上的context。如果路径以“/”开始,它被解释为相对于当前context根,如果路径包含查询字符,参数会被包含到接受组件的参数集的开始处。如果servlet容器由于任何原因不能返回
RequestDispatcher 该方法返回null。在
ServletContext类中有同名方法
public RequestDispatcher ServletContext.getRequestDispatcher(String path),不同之处在于ServletContext 中的方法(sevlet2.1引入)只接受绝对URLs(以/开头),而ServletRequest 中的方法接受绝对和相对。因此没有理由使用ServletContext中的该方法,可以看作只为历史版本兼容(不过servlet2.5仍然有此方法)。除了用路径还可以用名字指定资源来获得RequestDispatcher,ServletContext中的方法:
public RequestDispatcher ServletContext.getNamedDispatcher(String name),此方法允许转发到没必要公开发布的资源,servlet或jsp可能通过web应用描述符获得名字。不能获取RequestDispatcher仍然返回null。
RequestDispatcher有两个方法:forward()and include()。forward把整个请求交给代理;include把代理的输出加入调用servlet(个人理解为初始的那个servlet)的response中并且调用servlet仍然处于控制状态,即请求转发后,原先的Servlet还可以继续输出响应信息,转发到的Servlet对请求做出的响应将并入原先Servlet的响应对象中
forward把请求从一个servlet转发到服务器上的另一个资源,它允许一个servlet对请求做初始处理,然后另一个资源来产生response,不像sendRedirect,forward的整个操作都在服务器内部,对客户是透明的。附加查询字符串或使用请求的setAttribute的方法可以传递信息给代理 。
The rules a forwarding servlet must follow are relatively strict:
-
It may set headers and the status code but may not send any response body to the client (that's the job for an include). Consequently, the forward( ) must be called before the response has been committed.
-
If the response already has been committed, the forward( ) call throws an IllegalStateException.
-
If the response has not been committed but there's content within the response buffer, the buffer is automatically cleared as part of the forward.
-
In addition, you can't get creative by substituting new request and response objects. The forward( ) method must be called with the same request and response objects as were passed to the calling servlet's service method, and the forward( ) must be called from within the same handler thread.
如果servlet A转发到servlet B,B得到的路径信息应该是和直接调用B一样的,从这方面来说B完全控制请求。这也是为什么确保response的缓存会在调用之前冲掉,response没有被提交是很重要的事情。
用路径转发的问题是:目标组件不但对服务器组件可见,也必须对客户端可见。出于安全考虑,可能会让一些组件不是公共可见的,所以用名字代替路径做转发:getNamedDispatcher 不过没有URI路径也就不能加上查询字符串,路径也不能做调整。
forward 和sendRedirect谁更好:request dispatch 在服务器端发生,redirect在客户端发生。forward最好用在一个组件必须处理业务逻辑而且和另一个组件分享结果的情况。sendRedirict最好用于客户需要从一页重定向于另一页。看起来用forward比较有诱惑,因为forward在服务器内部完成,sendRedirect还需要从客户端倒一遍,所以forward更快。不幸的是处理相对URL的时候会引起问题。例:
public class HomePageForward extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
RequestDispatcher dispatcher = req.getRequestDispatcher("/index.html");
dispatcher.forward(req, res);
}
}
如果index.html中有<img src="../../../images/trans.gif"/>则图片不能读取。sendRedirect会告诉客户端文件来源于文件根目录,forward不会。因为不需要调用getContext查询,sendRedirect更容易转发到其他context的资源中。所以建议是尽量用sendRedirect,必须的情况下才用forward。
RequestDispatcher的include方法把资源的内容放入当前的respongse中。它实现了一种我们可以称为编程式服务器端 include。不同于forward,在调用include后的servlet A仍然控制response而且可能包含 被包含进来内容的前后内容。
例1:
doGet(..){
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<HTML><HEAD><TITLE>Welcome to Nile</TITLE></HEAD>");
out.println("<BODY>");
// Show an item in an online catalog
out.println("Feast your eyes on this beauty:");
RequestDispatcher dispatcher =
req.getRequestDispatcher("/servlet/NileItem?item=0596000405");
dispatcher.include(req, res);
out.println("And, since I like you, it's 20% off!");
out.println("</BODY></HTML>");
}
使用
forward,通过附加查询字符或
setAttribute方法,信息可以传到被调用资源。Using attributes instead of parameters gives you the ability to pass objects instead of simple strings,例
doGet(..){
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<HTML><HEAD><TITLE>Welcome to Nile</TITLE></HEAD>");
out.println("<BODY>");
// Show items in an online catalog
RequestDispatcher dispatcher =
req.getRequestDispatcher("/servlet/NileItem");
out.println("Feast your eyes on this beauty:");
req.setAttribute("item", Book.getBook("0596000405"));
dispatcher.include(req, res);
// Remove the "item" attribute after use
req.removeAttribute("item");
out.println("Or how about this one:");
req.setAttribute("item", Book.getBook("0395282659"));
dispatcher.include(req, res);
out.println("And, since I like you, they're all 20% off!");
out.println("</BODY></HTML>");
}
接受servlet
public class NileItem extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// We do not set the content type
PrintWriter out = res.getWriter();
Book book = (Book) req.getAttribute("item");
out.println("<BR>");
if (book != null) {
out.println("<I>" + book.getTitle() + "</I>");
out.println(" by " + book.getAuthor());
}
else {
out.println("<I>No book record found</I>");
}
out.println("<BR>");
}
}
通过实例可以很好理解include,就是把下一个资源的内容直接放入本页面的任何地方,被包含的资源不能改变status code or HTTP headers ,所以NileItem 做任何改变这些的操作都会被忽略(NileItem 所以没有做这些操作)。不像forward,路径元素和请求参数仍然和调用端一样。如果被包含资源要求获得自己的路径元素和请求参数, it may retrieve them using the following server-assigned request attributes:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
The request and response parameters must be the same objects as were passed to the calling servlet's service method, and the include( ) must be called from within the same handler thread.
The included resource must use an output mechanism that matches the caller's. If the caller uses a PrintWriter, then the included resource must use a PrintWriter. If the caller uses an OutputStream, then the included resource must use an OutputStream. If the mechanisms don't match, the included servlet throws an IllegalStateException. If you can, make sure to use the PrintWriter for all text output, and this won't be a problem.
servlet没有main方法,他受一个叫做容器的java应用(比如Tomcat)控制。当web server应用(比如Apache)收到容器访问servlet请求,server不是先把请求直接给servlet,而是先给部署这个servlet的容器,容器“看到”找的是servlet,创建两个对象:HttpServletResponse和HttpServletRequest。容器根据URL找到servlet类,为这个请求创建或从池里分配线程,把request和response对象传给该线程,servlet的生命周期就开始了。
容器启动的时候这步就发生了,一启动容器就开始找部署的web项目。第一寻找其servlet类,第二就开始加载这个类。A servlet moves from does not exist to initialized (which really means ready to service client requests), beginning with a constructor. But the constructor makes only an object, not a servlet. To be a servlet, the object needs to be granted servletness. 所以在构造函数和init()之间的状态是一种“半死半活状态”,这时候不能接触很多作为servlet能接触的资源(like getting web app configuration info, or looking up a reference to another part of the application),所以init()之前都不要做什么操作,不要在构造函数中放东西。在创建servlet实例之后在为任何客户端请求服务之前,容器就调用init方法,你可以在处理请求之前在里面初始化你的servlet(重写此方法可以建立数据库连接,用其他对象注册自己等)。接着调用service方法(重写此方法的可能性不大),接着doGet/doPost(至少重写其中一个方法)。service结束后,线程就消亡或者放回池里。但第二个请求来,容器再次创建或从池里分配线程,继续service->doGet/doPost 序列。因此,在一个时间段内,有多少客户请求就有多少运行的线程,当然也受容器的策略、配置或资源限制(比如指定容器的最大并发线程数,当请求的客户数超过的时候,客户就必须等待)。容器运行多个线程来处理多个对一个servlet的请求(不管是不是同一个客户发出请求,一个请求一个线程),对于分布式web应用,一个JVM只对应一个servlet实例
成为一个servlet可以给你什么:一个ServletConfig对象,一个ServletContext。
ServletConfig对象:每个servlet有一个;Use it to pass deploy-time information to the servlet (a database or enterprise bean lookup name, for example)that you don’t want to hard-code into the servlet (servlet init parameters);访问ServletContext;Parameters are configured in the Deployment Descriptor(如<init-param>方式配置).我们可以通过servletconfig获得初始化参数(getServletConfig().getInitParameter(“foo”);),然后通过setAttribute dispatch给某个jsp页面,但是针对一个servlet的初始化参数很有局限性,所以引入针对应用的<context-param>,这时候通过servletContext获得初始化参数(getServletContext().getInitParameter(“foo”)),这个初始化参数可以被应用的任意servlet,jsp访问。注意初始化参数是一个deploy-time constants,不能在runtime改变他们,只能通过重新部署。
ServletContext :每个web app有一个(严格说来如果是分布式的话,应该是每个JVM一个);Use it to access web app parameters (also confi gured in the Deployment Descriptor) ;类似application bulletin-board功能,让其他应用也能访问;用来获得服务器信息,比如容器名字和版本,以及支持的API版本。ServletContextListener 可以监听ServletContext 的初始化(从ServletContext中获得初始化参数,用参数连接数据库,把连接作为attribute存起来以便所有应用都可以访问)和销毁阶段(关闭连接)。为了使context attributes 线程安全,可以对context进行同步锁的方式:
synchronized(getServletContext()) {
getServletContext().setAttribute(“foo”, “22”);
getServletContext().setAttribute(“bar”, “42”);
out.println(getServletContext().getAttribute(“foo”));
out.println(getServletContext().getAttribute(“bar”));
}
ServletResponse接口有两种流可供输出:ServletOutputStream for bytes, or a PrintWriter for character data.
例:1 PrintWriter writer = response.getWriter();
writer.println(“some text and HTML”);
2 ServletOutputStream out = response.getOutputStream();
out.write(aByteArray);
如果不想自己处理,可通过redirect给一个全新的URL(注意如果response已提交后不能再redirect),或者dispatch the request 给你应用中的某个组件(如jsp)。redirect会先返回给客户,客户再重新发请求,而dispatch 的话,服务器会直接转给某个组件。这样两种方式的区别就很明显了。
servlet->容器调用service()->doGet()/doPost() 产生动态页面并把页面放入response对象,记住此时容器仍然有引用指向该response对象!线程完成,容器把response对象转换为HTTP对象,送回客户端,然后删除request和response对象。
jsp中的所有东西都可分为两大类:
elements 和
template data。elements 是jsp的动态部分,他们按照jsp规范作为自定义操作,标签,以及之间的允许内容,jsp容器会解释elements ,当产生response的时候elements 定义的操作会运行(dynamic)。template data是静态文本部分,他们被任意放在jsp页面上直接发送给客户端(not change)。
elements 又可分为三类:scripting elements, directives, and actions 。
scripting elements :有三种类型:
1 Scriptlets即常见到的<%.... %>,
注意scriptlet 的内容不会发送给客户端,只有scriptlet 的结果会发送,这表明scriptlet 被容器解释,scriptlet 的代码默认情况下不会被访问者分享。 代码编译成servlet后在jspService() 内。这里面写的java代码最后结果以out.print(…);输出
2 Expressions:
<%=...=>: 发送字符串给客户端,表达式可以是任意有toString方法的对象,或者是primitive。代码编译成servlet后在jspService() 内。不要在后面加分号,否则会以out.print(...;); 形式输出,引起编译错误
3 Declarations:<!=...=>: declaration 就像scriptlet一样用来向jsp嵌入代码,这些声明编译成servlet后都是在jspService() 方法外面的,所以声明里可用来定义新方法(可以被scriptlet调用)或全局类变量,但声明里代码不是线程安全的,必须自己控制。 directives 并不用来向客户端输出,用来定义页面属性,包括定制标签库和包含其他页面,语法 :
<%@ directive {attribute="value"}* %>
三种不同的jsp directives :
page,
taglib, and
include。
<%@ page %>给jsp容器提供页面的相关信息
<%@ include %> is used to include text and/or code at translation time of a JSP,语法<%@ include file="relativeURL" %>,这个文件必须是web 应用的一部分,由于在转换阶段被包括,相当于在编译之前把文件源代码直接放进来,所以不影响runtime阶段的性能。<jsp:include /> 在runtime阶段触发,虽然效能没前面translation time include好,但保证被包括文件的实时性。
<%@ taglib %> 自定义标签库,语法<%@ taglib uri="uri" prefix="prefixOfTag" %> Actions 可以方便的把动态代码链入简单的jsp页面,功能类似scripting elements ,但是把和jsp打交道的java代码抽象出来了,有两种:standard and custom 语法
<prefix:element {attribute="value"}* />
jsp中有implicit objects,就像在Servlets中的对象,在scripting elements中可以使用:config,request,response,session(默认jsp持有一个session,在scripting elements 用这个对象,就像调用HttpServletRequest getSession() 一样),application(javax.servlet.ServletContext 对象实例,就像调用ServletConfig getServletContext() 一样) 还有一些没有直接和Servlet 对应的,但是隐含的对象:
pageContext(含有一个jsp的许多对象资源信息,在定制标签的时候用得较多),
page,
out, and
exception