Servlet & JSP

WEB应用的演变

  1. 单机程序。缺点:无法协同办公。
  2. 网络程序(主机+终端模式)。缺点:主机售价及维护成本高。
  3. 网络程序(两层CS架构:客户端+数据库)。缺点:数据库编写业务逻辑可移植性差、连接数有限。
  4. 网络程序(三层CS架构:客户端+应用服务器+数据库)。缺点:数据通信模块增加开发难度。
  5. 网络程序(BS架构:浏览器+Web服务器+数据库)。优点:不用编写通信程序!

什么是Web服务器:把本地的资源公开给外部访问的服务器。

什么是Servlet:Web服务器中处理动态资源请求的扩展程序。

  1. CGI(Common Gateway Interface通用网关接口):Web服务器的早期的扩展程序,用于处理客户端动态资源的请求(早期的Web服务器只能处理静态资源的请求)。缺点:开发复杂,性能较差(一个请求一个进程),可移植性不好。
  2. Servlet技术:是使用Java语言开发的一套组件规范,性能好(一个请求一个线程),可移植性好,已经取代了CGI技术。

什么是动态网页:用户多次访问这个网页时,网页的源码是有可能改变的。

常用动态web资源开发技术:Servlet/JSP、ASP、PHP等。

常用的Servlet容器兼Web服务器:Tomcat(Apache) / JBOSS(Redhat) / glasshfish / WebLogic(BEA) / WebSphere(IBM)。

WebLogic和WebSphere支持JavaEE规范,Tomcat不完全支持(如EJB分布式应用)。

WebLogic和WebSphere收费;JBOSS软件免费,服务收费;Tomcat免费。

Tomcat的使用

  1. 下载地址(官网):Apache Tomcat® - Welcome!
  2. 压缩版的直接解压,无需安装。
  3. 配置环境变量:
    1. 需有JAVA_HOME或JRE_HOME系统变量
    2. 若有CLASS_PATH系统变量,其值前端添加:%CATALINA_HOME%\lib\servlet-api.jar;
    3. (可选)新建系统变量CATALINA_HOME,值为Tomcat目录名(bin的上级目录)
    4. (可选)添加系统变量Path的值(原值前端添加):%CATALINA_HOME%\bin;
  4. 启动和关闭(Linux下):
    1. 获取权限:在Tomcat的bin目录右键打开终端输入 chmod +x *sh 以获取访问权限
    2. 启动:再输入 startup.sh 以启动Tomcat
    3. 验证:浏览器输入 http://localhost:8080 可访问Tomcat主页,则启动成功
    4. 关闭:再输入 shutdown.sh 则关闭Tomcat
  5. 启动和关闭(Windows下):
    1. 启动:在Tomcat的bin目录下运行startup.bat文件或执行startup命令(已配置环境变量的,可在任意目录下执行)
    2. 验证:浏览器输入 http://localhost:8080 可访问Tomcat主页,则启动成功
    3. 关闭:在同目录下运行shutdown.bat文件或执行shutdown命令(已配置环境变量的,可在任意目录下执行)
  6. 配置、启动和关闭(Eclipse下):
    1. 配置Tomcat服务器:Window → Preferences → Server → Runtime Environments → Add… → 选择Apache Tomcat版本 → 勾选Create a new local server → Next → Browse… → 选择Apache Tomcat安装目录 → Finish → OK
    2. 设置Tomcat服务器:Window → Show View → Servers → 双击服务 → Server Locations → 选择第二项 Use Tomcat installation (…) → 保存设置
    3. 启动和关闭(略)
  7. 启动故障解决:
    1. 闪退:原因:未配置JAVA_HOME。(Tomcat由java开发,需要用jvm运行)
    2. 闪退、java.net.BindException: Address already in use:绑定异常,端口被其他程序或已在运行的Tomcat实例占用。修改Tomcat监听端口方法:修改server.xml配置文件中属性protocol="HTTP/1.1"的Connector元素的port属性值。查看端口占用情况用cports.exe软件。
    3. CATALINA_HOME配置导致无法访问指定资源:修改CATALINA_HOME路径使其指向期望路径或删除CATALINA_HOME环境变量(安装版的Tomcat会自动设置CATALINA_HOME,卸载后不会自动删除)。

Tomcat的目录结构

|-bin 存放Tomcat操作命令。bat是window版本,sh是Unix和linux版本。

startup.bat : 后台在调用catalina.bat start

shutdown.bat  : 后台在调用catalina.bat stop

|-conf 存放Tomcat服务器软件的配置文件。server.xml文件是核心配置文件。

|-lib 支撑Tomcat软件运行的jar包。

|-logs 存放Tomcat服务器运行过程中日志信息。

|-temp Tomcat的临时目录,存放临时文件。

|-webapps 存放web应用的目录。

|-work Tomcat运行目录。存放jsp页面运行过程中产生的临时文件。

web应用目录结构(规范)(重点)

|-根目录:名称自定。一个web应用必须有一个根目录。根目录下的文件外界可以直接访问!

|-静态资源:html+css+javascript+images+xml。

|-WEB-INF目录:该目录下文件默认外界无法直接访问!由web服务器负责调用!可在web.xml文件中设置可访!

|-classes目录(可选):放class字节码

|-lib目录: (可选):存放jar包。不能有子目录,全部jar包放在根目录下。

|-web.xml文件: web应用的配置文件。

web应用部署三种方法(推荐第三种)

1)直接把web应用拷贝到webapps目录下

使用默认资源:访问默认资源,浏览器不需要写web资源名称。

在web.xml文件中配置默认资源:

    <!-- 默认资源--><!—文件内容若有中文,须保证编码:<?xml version="1.0" encoding="UTF-8"?> -->

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.jsp</welcome-file>

<welcome-file>hello.html</welcome-file>

</welcome-file-list>

使用默认web应用名:ROOT是默认应用名。访问默认应用名,浏览器不需要写web应用名称。

将web应用根目录名改成ROOT即可使用默认web应用名。

使用默认端口:80是默认端口。访问默认端口,浏览器不需要写端口号。

在server.xml配置文件中配置默认端口:将属性protocol="HTTP/1.1"的Connector元素的port属性值改为80。

弊端:Tomcat软件和web应用绑定在一起。

解决办法:使用虚拟web应用(虚拟网站)把tomcat软件和web应用分离。

2)虚拟网站配置

在%CATALINA_HOME%/conf/server.xml文件中修改:Host节点下增加Context子节点

<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">         

<!-- 配置虚拟网站,path值前面的“/”可省略 -->

<Context docBase="web应用根目录或WAR文件的绝对路径或相对Host之appBase路径的相对路径"

 path="/web应用名称"/>

</Host>

注意:

如果path为空字符串或“/”,那么浏览器也不需要写web应用名称访问该web应用,而且优先于ROOT应用。

弊端:修改了server.xml核心配置文件,风险比较高。

解决办法:添加xml文件配置虚拟网站。

3)通过添加xml文件的方式配置虚拟网站(推荐)

在%CATALINA_HOME%/conf/Catalina/localhost目录下,添加xml文件(文件名就是web应用名):

<?xml version="1.0" encoding="utf-8"?>

<Context docBase="web应用根目录的绝对路径"/>

模拟建立站点(Host)

站点和网站的关系:一个站点可以包含多个网站。

1)添加站点:在server.xml文件中添加Host节点,name属性指定站点名称,appBases属性指定站点根目录。

<!-- 新建站点-->

<Host name="www.baidu.com"  appBase="E:\baidu"

unpackWARs="true" autoDeploy="true"

xmlValidation="false" xmlNamespaceAware="false">

</Host>

2)“注册”域名:在本地的hosts文件(C:\Windows\System32\drivers\etc\hosts)中添加一个站点和ip地址的映射:

127.0.0.1    www.baidu.com

3)访问站点及其网站:访问站点的百度音乐项目:

http://www.baidu.com/music/index.html

Tomcat详解

server.xml文件

<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">

<Listener className="org.apache.catalina.startup.VersionLoggerListener" />

<Listener className="org.apache.catalina.core.AprLifecycleListener"

SSLEngine="on" />

<Listener className="org.apache.catalina.core.JasperListener" />

<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

<GlobalNamingResources>

<Resource name="UserDatabase" auth="Container"

type="org.apache.catalina.UserDatabase" 

description="User database that can be updated and saved"

factory="org.apache.catalina.users.MemoryUserDatabaseFactory"

pathname="conf/tomcat-users.xml" />

</GlobalNamingResources>

<Service name="Catalina">

<Connector port="8080" protocol="HTTP/1.1"

connectionTimeout="20000" redirectPort="8443" />

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

<Engine name="Catalina" defaultHost="localhost">

<Realm className="org.apache.catalina.realm.LockOutRealm">

<Realm className="org.apache.catalina.realm.UserDatabaseRealm"

resourceName="UserDatabase" />

</Realm>

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" 

prefix="localhost_access_log." suffix=".txt" 

pattern="%h %l %u %t "%r" %s %b" />

</Host>

</Engine>

</Service>

</Server>

组件关系

顶层类组件:Server、Service

连接器类组件:Connector

容器类组件:Engine、Host、Context

嵌套类组件:Logger、Valve、Realm

server.xml文件结构

<Server>    代表整个Catalina Servlet容器

<Listener />

<GlobaNamingResources>

</GlobaNamingResources>

<Service>...    不同端口部署不同应用则用多个Service

<Connector />...    连接器。接收客户端请求,发送服务端响应。可多个,共享同一个<Engine>。

<Engine>    一组虚拟主机的集合。处理在同一个Service中所有Connector接收到的客户请求并生成响应结果。

<Logger />

<Realm />

<Host>...    虚拟主机,一个Host对应一个虚拟主机,一个虚拟主机可简单理解为一个网站。

<Logger />

<Context />...    Web应用程序,一个Context对应一个Web应用程序。

</Host>

</Engine>

</Service>

</Server>

server.xml参数详解

元素名

属性

解释

Server

(非容器,不可定义“Valves”或“Loggers”子组件)

port

指定一个端口,这个端口负责监听关闭Tomcat的请求

shutdown

指定向端口发送的命令字符串

Service

(非容器,不可定义“Valves”或“Loggers”子组件)

name

指定Service的名字。

值为Catalina时,处理由Tomcat服务器直接接收的web客户请求;

值为Apache时,处理由Apahce服务器转发过来的Web客户请求。

Connector(连接器,连接客户端和服务器)

port

指定服务器端要创建的端口号,并在这个断口监听来自客户端的请求。

端口号默认8080,若为80,在请求地址中可忽略。

protocol

指定Http协议,默认值为HTTP/1.1。常用协议:

org.apache.coyote.http11.Http11Protocol:HTTP/1.1,一个访客网络连接需要一个线程,并发性能比较低。

org.apache.coyote.http11.Http11NioProtocol:NIO连接器,一个由非阻塞的socket工作模式构成的连接器,并发性能良好,纯Java实现。

org.apache.coyote.http11.Http11AprProtocol:APR连接器,所谓 APR 就是网络上最多服务器使用的Web服务程序Apache Http Server所使用的库,Tomcat建议在生产环境使用。

minProcessors

服务器启动时创建的处理请求的线程数,默认值5?

maxThreads

(default=200) 最多可创建线程的数量。

maxProcessors

最大可以创建的处理请求的线程数,默认值200

executor

指定当前连接器使用的线程池的名称,如果指定,则忽略其他针对线程数量的设置,比如 maxThreads。

EXECUTOR 的配置:

Executor 用于定义共享的线程池。默认情况下每个Connector都会产生自己的一个线程池,如果你想多个Connector共享一个线程池,则可以先定义一个线程池,如:

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>

然后增加Connector的executor属性:

executor="tomcatThreadPool"

maxPostSize

最大允许POST上传的数据大小(单位为字节),默认为2097152字节即2MB。对于一般网站来说,比如有写评论写文章的网站,默认的2MB已经足够,不过如果网站带有图片甚至文件上传功能,则需要根据具体情况来定。

enableLookups

默认值为true,支持解析客户端域名。WEB应用中可通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址

redirectPort

指定服务器正在处理http请求时收到了一个SSL传输请求(https)后重定向的端口号。

当用户访问非https的资源而该资源又需要https方式访问时,Tomcat会自动重定向到https端口,一般https使用 TCP 443端口,所以一般取值”443″。

SSLEnabled

(默认为false),设置当前连接器是否使用安全SSL传输,如果设置为”true”,则应该同时设置下面两个属性: scheme="https" (默认为http),secure="true"(默认为false)。

acceptCount

指定当所有处理请求的线程都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理(返回Connection refused错误)

adress

连接器所绑定的IP地址,当一台服务器存在多个ip地址时可以指定其中的需要绑定的一个,默认不设置该属性的值表示绑定当前服务器的所有ip地址。

compressableMimeType

(default=”text/html,text/xml,text/plain”) 指定需要GZIP压缩的资源的类型。

compression

(default=off) 是否启用GZIP压缩,可以取值 on/off/force,设置为on之后会对 compressableMimeType 属性指定的资源类型启用GZIP压缩。

keepAliveTimeout

(default=connectionTimeout),访客完成一次请求后维持网络连接的时间。

connectionTimeout

指定超时的时间毫秒数(当访客网络连接后服务器等待第一行Request头出现的时间),默认为60000,若为-1表示不限制。

Engine(每个Service只能有一个Engine.处理在同一个Service中所有Connector接收到的客户请求)

defaultHost

指定缺省的处理请求的主机名,它至少与其中的一个host元素的name属性值是一样的

Host(表示一个虚拟主机)

name

设置虚拟主机的域名,比如localhost表示本机名称,实际应用时应该填写具体域名,比如www.dog.com或者dog.com,当然如果该虚拟主机是给内部人员访问的,也可以直接填写服务器的ip地址,比如 192.168.1.10。

alias

指定主机别名,可以指定多个别名

appBase

设置 Web 应用程序组的路径,即存放应用程序的目录。

appBase属性的值可以是相对于Tomcat安装目录的相对路径,也可以是绝对路径,需要注意的是该路径必须是Tomcat有权限访问的,通过Arch Linux源安装的Tomcat是通过Tomcat用户运行的,因此创建一个新的appBase目录之后可以使用chown命令更改目录的所有者。

autoDeploy

是否允许自动部署,默认true,表示Tomcat服务处于运行状态时,能够监测appBase下的文件,如果有新有web应用加入进来,会自运发布这个WEB应用

unpackWARs

默认true,自动展开war压缩包再运行Web应用程序,为false则不解压,直接从WAR文件中运行应用程序。

deployOnStartup

若为true,表示Tomcat服务器启动时会自动发布appBase目录下所有的Web应用.如果Web应用中的server.xml没有相应的<Context>元素,将采用Tomcat默认的Context

Context(表示一个web应用程序,通常为WAR文件,关于WAR的具体信息见servlet规范)

docBase

应用程序的路径或者是WAR文件存放的路径

path

表示此web应用程序的url的前缀,这样请求的url为http://localhost:8080/path/****

reloadable

默认false。若为true,则Tomcat会自动检测应用程序的/WEB-INF/下lib和classes目录的变化,自动重新加载Web应用,我们可以在不重启Tomcat的情况下改变应用程序。开发时非常有用,但开销大;产品发布时不建议使用。

cookies

指定是否通过Cookies来支持Session,默认值为true

useNaming

指定是否支持JNDI,默认值为了true

Logger(表示日志,调试和错误信息)

className

指定logger使用的类名,此类必须实现org.apache.catalina.Logger 接口

prefix

指定log文件的前缀

suffix

指定log文件的后缀

timestamp

如果为true,则log文件名中要加入时间,如下例:localhost_log.001-10-04.txt

Realm(表示存放用户名,密码及role的数据库)

className

指定Realm使用的类名,此类必须实现org.apache.catalina.Realm接口

Valve(功能与Logger差不多,其prefix和suffix属性解释和Logger 中的一样)

className

指定Valve使用的类名,如用org.apache.catalina.valves.AccessLogValve类可以记录应用程序的访问信息

directory

指定log文件存放的位置

pattern

有两个值,common方式记录远程主机名或ip地址,用户名,日期,第一行请求的字符串,HTTP响应代码,发送的字节数。combined方式比common方式记录的值更多

什么是WebLogic

BEA WebLogic是美国Oracle公司出品的一个基于JAVAEE架构的中间件,是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器(application server)。

什么是中间件(middleware)

中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源,中间件位于客户机服务器的操作系统之上,管理计算资源和网络通信。

顾名思义,中间件处于操作系统软件与用户的应用软件的中间。

WebLogic与Tomcat比较

WebLogic特点

Tomcat特点

WLS全面支持J2EE的标准规范和其他标准规范,Web Service, SSL, xml,EJB等

Tomcat 为WEB容器,只支持部分J2EE标准, 应用局限性强,不支持EJB

完善的售后支持

没有售后支持, 看社区与论坛

集群机制, 支持分布式的应用

需要结合第三方插件/应用

Web控制台进行组件、JDBC、管理和配置

较好的支持热部署(开发模式下)

需要费用

开源免费

什么是集群(负载均衡/故障转移)

在集群系统中,来自客户的请求可以进行分配,把相应的进程分发给与之共同承担任务的服务器,从而不影响应用进程的运行,大多数中间件都支持负载均衡,实现负载均衡大大降低了系统的崩溃现象,从而减少对企业带来的损失。

故障转移:软硬件出现故障,能够有其它相关的软硬件设备来承接相关的工作任务,以保障系统正常工作。

什么分布式

分布式研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给许多计算机进行处理,最后把这些计算结果综合起来得到最终的结果。分布式网络存储技术是将数据分散的存储于多台独立的机器设备上。分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,不但解决了传统集中式存储系统中单存储服务器的瓶颈问题,还提高了系统的可靠性、可用性和扩展性

下载WebLogic

Oracle | Cloud Applications and Cloud Platform → Downloads → Middleware → WebLogic...

10.3.6是企业中最多使用的最稳定的版本。

三种安装包:(OEPE指Oracle Enterprise Pack for Eclipse

Server + Coherence + OEPE

Server + Coherence

Server

安装WebLogic

  1. Windows版12c解压后是jar文件,当前目录CMD运行命令安装:java -jar 文件名
  2. 取消自动更新选项
  3. 选择定制安装
  4. 选择所有产品和组件
  5. JDK选择:取消捆绑的SUN SDK,选择本地自己的JDK, 和安装JRockit(可选)。
    Oracle Jrockit是专门为在基于英特尔处理器的高性能服务器上运行大规模的关键任务型的服务器端应用而设计。
    若为64位的 WebLogic,默认无 JRockit 选项,可到 Oracle 官网下载单独安装。

WebLogic目录结构

…\Oracle\Middleware\Oracle_Home介绍

coherence 集群组件。在可靠的、高度可伸缩的对等集群协议之上提供了复制的、分布式的(分区的)数据管理和缓存服务

logs 系统日志目录

modules 第三方jar包

user_projects 存放域的文件夹

utils 工具包

wlserver_10.3 WebLogic的主目录

…\Oracle\Middleware\Oracle_Home\user_projects\domains\base_domain介绍

autodeploy 存放管理服务器部署的项目,在开发模式下将自动展开服务(ear、war)(尽量不要在管理服务器中部署项目)

bin 存放域中的可执行文件

config 本域相关的配置文件

console-ext 控制台信息, 只应用于adminServer

lib 存放库文件

pending 待激活的本域配置文件

security 安全相关

servers 包含域的所有服务

startWebLogic.cmd 调用bin下面的StartWebLogic.cmd

startWebLogic.sh 调用bin下面 StartWebLogic.sh (Linux下)

…\Oracle\Middleware\Oracle_Home\wlserver介绍

common 由产品组件共享的文件,包括计算机上运行的所有WebLogic域所公用的环境属性, 创建域提供Configuration Wizard和

WLST脱机使用的模板JAR文件

server WebLogic Server程序文件

uninstall 卸载WebLogic产品目录

WebLogic概念介绍

Domain(域)

域是一组逻辑相关的 WebLogic 服务器(Server)的集合。WebLogic 服务器分为管理服务器(Administration server)和受管服务器(Managed Servers)。一个域有且只有一个管理服务器(创建域后自动生成,默认端口7001),可有多台受管服务器,或者受管服务器集群。

域的默认目录为: …\Oracle\Middleware\user_projects\domains\base_domain

…\base_domain\config\config.xml:域的配置文件。

Sever是JVM的一个实例。一个Server:在机器上运行,且拥有自己的内存空间,且为多线程。

Administrator Server(管理服务器)

管理服务器负责 domain 的管理(启动后台控制面板和管理其它服务器),一般不负责应用。管理服务器是控制整个域配置的中心操作节点,管理服务器维护着整个域的配置并将配置分配到每个受管服务器中。每个域中都必须有一个管理服务器。

Admin Server如果挂了,对于Domain中的ManagedServer不会产生影响,如果配置了集群,对于负载均衡和Failover机制也不会产生影响,因为这些都是基于配置文件的(config.xml),而不依赖于 AdminServer 的实例。Admin Server 不必时刻运行,当我们需要访问控制台、修改配置或者部署应用时,把 Admin Server 运行起来就行!

…/base_domain/autodeploy:用于存储管理服务器部署的项目,在开发模式下将自动展开服务(ear、war)(尽量不要在管理服务器中部署项目)。

…/base_domain/servers/AdminServer:存储管理服务器的配置,其中 security 存储了用户与密码的相关信息。

Managed Server(受管服务器)

受管服务器负责部署实际的应用。他从Admin Server那里获取配置信息。通常在Managed Server上部署自己的服务、组件或应用。基于性能考虑,Managed Server维护着一份只读的域配置文件,当服务启动的时候,会从Admin Server上同步配置文件信息,如果配置文件发生了改变,Admin Server也会将改变推送到Managed Server上。

Machine(计算机)

一个Machine代表一台运行 WebLogic Server 的实在的机器,包括其 IP 地址等信息,通常与部署在其上的 Managed Server 相关连。Machine 用于Node Manager(一个运行在物理服务器上的进程)。一个域中可以包括多台机器。一个机器可以运行多个 Weblogic Server 实例。

Cluster(集群)

集群是一组WebLogic Server的组合,集群是为了满足服务的高可用(High Avilability)以及可扩展(Load Balancing)需求而出现的。需要注意的是,集群中的WebLogic Server必须版本号一致。Cluster需要注意的几个问题:不能够跨Domain、Cluster中的服务器必须在同一个域中、Domain中可以有多个Cluster

eclipse配置WebLogic

1)Window->Preferences->Server->Runtime Environment点击Add按钮,并选择Oracle下的WebLogic,然后点 next 根据界面提示往下走。

2)根据提示重启eclipse后,重走第1)步至选择选择Oracle下的WebLogic后点Next。

3)在 Define a WebLogic Runtime 界面,WebLogic home 一栏选择文件夹 …\Oracle\Middleware\Oracle_Home\wlserver ;Java home 一栏选择jdk的JAVA_HOME。

MyEclipse配置WebLogic

找到配置页面

Preferences -> Servers -> Weblogic -> 选择版本

配置WebLogic与Tomcat相同

项目部署到WebLogic中

  1. 目前WebLogic版本仅仅支持J2EE5.0 因此要把web.xml中的配置文件改成2.5规范

<web-app version="2.5" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

启动与服务相关配置

WebLogic的启动是通过启动文件(在域的bin目录中)来完成的,不同的服务定义在不通的启动配置文件中。

StartWebLogic 启动管理服务器

StartManagedWebLogic 启动受管服务器

StopWebLogic 关闭管理服务器

StopManagerWebLogic 关闭受管服务器

访问地址

管理控制台访问地址:本地的话是 http://localhost:7001/console ,远程的话把地址和端口改一下,一般端口为7001。

项目访问地址:本地的话是 http://localhost:7001/项目名 ,远程的话把地址和端口改一下,一般端口为7001。

WebLogic生命周期

  1. SHUTDOWN 状态下,WebLogic Server 实例已配置但处于非活动状态
  2. STARTING 状态期间,由于执行"启动"、"管理模式启动"或"以待机模式启动"命令,WebLogic Server 实例从 SHUTDOWN 转换为 STANDBY
  3. STANDBY 状态的服务器实例不处理任何请求,它的常用监听端口处于关闭状态。管理端口处于打开状态,可以接受将服务器实例转换为 RUNNING 或 SHUTDOWN 状态的生命周期命令
  4. ADMIN 状态下,WebLogic Server 启动并运行,但仅用于管理操作,您可以在此状态下执行服务器级和应用程序级管理任务
  5. 在此转换状态中,WebLogic Server 执行将其从 STANDBY 或 ADMIN 状态转换为 RUNNING 状态所需要的操作
  6. RUNNING 状态下,WebLogic Server 处于完全工作状态,可以向客户端提供服务并作为一个完整的群集成员运行

创建WebLogic域

  1. 运行开始菜单中Oracle的“Configuration Wizard”。
  2. 创建域:选择“创建新的WebLogic 域”,并选择域的位置(默认即可)。
  3. 模板:选择“使用产品模板创建域”并选择产品模板。
  4. 管理员账户:设置管理控制页面登录的用户名和密码(设置后,自己要记住)。
  5. 选择域模式和默认的JDK。开发模式启用自动部署,生产模式关闭自动部署(MyEcipse版本不支持产品模式)。
  6. 高级配置:选择“管理服务器”。
  7. 管理服务器:最好使用默认的7001端口,不要修改,避免后续出现不必要的麻烦。
  8. 配置概要:确认无误后点“创建”,等待域配置完成。
  9. 运行 ...\Oracle\Middleware\Oracle_Home\user_projects\domains\base_domain\starWebLogic.cmd 以启动服务器(Windows)。
  10. 浏览器访问 http://localhost:7001/console 进入WebLogic Server管理控制台登录界面,输入用户名、密码(在创建域时设置的)。
  11. 登录成功后,进入WebLogic Server管理控制台主界面。

创建受管服务器、MSI、节点管理器

创建受管服务器

  1. 运行starWebLogic.cmd并登录到控制台,找到 域结构 -> 环境 -> 服务器,系统创建域时已自动生成主服务器 AdminServer (管理)。
  2. 点“新建”,配置受管理服务的相关信息:
    名称:自定义。
    监听地址:若在本机建立服务器,则默认可不填;若管理其他机器的服务器,则填被管机器的ip地址。
    监听端口:不能与主服务器端口相同。
    集群选择:暂时不进行集群配置
  3. 创建好的受管理服务器,默认状态是SHUTDOWN,可通过命令启动它:
    StartManagedWebLogic.cmd 受管服务器名 http://主服务器监听地址:主服务器端口号
    如果管理服务器和受管服务器在同一台机器上,执行以上命令时后面不用再加上链接地址。
    启动后在 base_domain\servers\ 会新增加一个Server-0服务。
  4. 注意如果受管服务器启动提示内存不足,则可以在后台控制器中配置启动内存:
    单击该受管服务器 -> 服务器启动 -> 参数: -Xms512m -Xmx512m -XX:MaxPermSize=512m

MSI介绍

  1. Managed Server Independence受管服务器独立性:受管服务器拥有一份域配置信息的副本.当受管服务器启动的时候,它会从管理服务器获取到配置信息.如果受管服务器启动的时候不能连上管理服务器. 就使用本地的缓存配置信息启动.这就是MSI模式. MSI模式默认开启的
  2. 如何开启MSI功能:选中”受管服务器”--->”优化”--->”高级”---->”受管服务器独立性”
  3. 在MSI模式下, 受管服务器:自动寻找本地config目录下的config.xml文件.寻找本地security目录下的SerializedSystemlni.dat文件和boot.Properties文件

使用WLST工具

  1. WebLogic Scripting Tool (WSLT)是一个命令脚本工具, 可以用来创建、管理、监控WebLogic域.它基于Jython语法编写的可以在:
  2. WL_HOME\common\bin\wlst.cmd 中启动WLST,也可以在开始菜单启动WLST
  3. Online模式(使用WLST连接正在运行的管理服务器):管理活动域的配置、查看域中的性能数据、管理安全数据(例如添加删除用户等......)
  4. 使用WLST连接受管服务器,但是不能通过被管理的服务器修改配置
  5. Offline模式(使用WLST没有连接管理服务器):可以创建域模板、创建域、扩展活动域等等
  6.  netstat -a -n  // 查看网络端口
  7. help('all')  // 查看所有命令帮助
  8. help('connect')   // 查看connect命令的帮助
  9. connect ('weblogic','weblogic123','t3://localhost:7001')
  10. connect ('weblogic','weblogic123')
  11. disconnect()  ls() 查看  exit()
  12. State('AdminServer'): 目前是RUNNING状态
  13. 挂起:suspend() 恢复:suspend()  关闭:shutdown()

集群配置

创建两台machine(相当于一台实际的物理主理),用来管理远程的weblogic实例。

在控制台中找到 域结构 -> 环境 -> 计算机 -> 新建,创建两台计算机Machine-2和Machine-3

创建两台用于部署应用的受管服务器 Server-2, Server-3 和一台用于HTTP代理的受管服务器 Proxy

名称

监听地址

监听端口

Server-2

127.0.0.1

7002

Server-3

127.0.0.1

7003

Proxy

127.0.0.1

7000

将应用部署到受管服务器 Server-2 和 Server-3 中

域结构 -> 部署 -> 安装 -> 选择应用 -> 下一步 -> 默认将此部署安装为应用程序 -> 下一步 -> 选择受管服务器 -> 下一步 -> 下一步 -> 完成

启动受管服务器 Server-2 和 Server-3

在 ...\user_projects\domains\base_domain\bin  目录下打开两个CMD运行下面两条命令(过程需要输入管理服务器的管理员用户及密码):

startManagedWebLogic.cmd Server-2 http://127.0.0.1:7001

startManagedWebLogic.cmd Server-3 http://127.0.0.1:7001

部署测试

域结构-》部署-》选中应用--》测试--》选中对应的URL,或者直接访问:

http://127.0.0.1:7002/应用名/index.jsp

http://127.0.0.1:7003/应用名/index.jsp

访问没问题则代表服务器运行正常,但是访问的时候是通过7002/7003端口访问的. 此方式并不是集群。

创建集群

  1. 在控制台中找到 域结构 -> 环境 -> 集群 -> 新建(集群)。
  2. 配置集群属性:
    名称:自定义,例如Cluster-0
    消息消息传送模式:选多点传送。单点指单机到单机,多点指服务端直接发送到每台电脑,效率高,且硬件基本支持。
    多点传送地址:默认即可.
    多点传送端口:7001已被管理服务器占用,此处指定一个新的空闲端口,例如7777。
  3. 停止Server-2、Server-3受管服务器并把它们添加到集群Cluster-0中。注意管理服务器是不能添加到集群中的,管理服务器是用来管理受管服务器和设置配置信息的,并不是用来部署和发布项目的。

创建HTTP代理应用程序

  1. 首先在开始程序菜单打开代理服务器向导 Configuraction Wizard
  2. 选择扩展(或更新)现有的Welogic域, 我们要给被集群的服务器创建一个代理程序
  3. 选中要扩展的域,此处选中…\Oracle\Middleware\Oracle_Home\user_projects\domains\base_domain
  4. 使用产品模板更新域:选中代理的方式 Weblogic Advanced Web Servs for JAX-RPC Extension,就是基于XML的远程过程调用协议。JAX-WS则是基于XML的WebService服务。此处我们需要通过XML配置被代理服务器的IP信息,然后代理服务器把请求获取后交给被代理服务器。
  5. 高级配置:选中和集群相关的选项。
  6. 受管服务器配置:由于前面已经配置好了,此处不需要修改
  7. 集群配置:如果需要修改集群的配置则单击修改
  8. 向集群分配受管服务器:此处遵循前面的配置即可
  9. 创建HTTP代理应用程序:选框打钩,并选择之前创建的用于HTTP代理的受管服务器Proxy
  10. 询问是否添加计算机,没有计算机,就不用添加
  11. 将部署定位到集群或服务器:建议在控制面板中指定,此处默认即可
  12. 将服务定位到集群或者服务器
  13. 配置概要:确认无误点“扩展”
  14. 扩展完毕后会自动生成代理项目,路径为 user_projects\domains\base_domain\apps ,内含配置文件Web.xml和Weblogic.xml。
  15. Web.xml中配置如下:
  16. Weblogic.xml中配置访问项目的前缀:
  17. 把代理项目部署到代理服务器:
    域结构 -> 部署 -> 安装 -> 选择代理项目 -> 下一步 -> 默认将此部署安装为应用程序 -> 下一步 -> 选择代理服务器 -> 下一步 -> 下一步 -> 完成
  18. 集群测试:
    域结构-》部署-》选中代理项目-》测试-》选中对应的URL,或者直接http://127.0.0.1:7000/受管服务器的应用名,可以看到代理服务器对不同会话随机分配 Server-2 和 Server-3 受管服务器(通过jsp代码 <%=application.getRealPath("/") %> 查看)

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<!--HttpClusterServlet来拦截请求,然后转发给被代理的服务器  -->

<servlet>

<servlet-name>HttpClusterServlet</servlet-name>

<servlet-class>weblogic.servlet.proxy.HttpClusterServlet</servlet-class>

<!-- 配置了集群的服务器, 服务器与服务器之间用 |隔开 -->

<init-param>

<param-name>WebLogicCluster</param-name>

<param-value>127.0.0.1:8001|127.0.0.1:8002</param-value>

</init-param>

</servlet>

<!-- 配置了需要拦截的 路径,与后缀,根据情况自行添加 -->

<servlet-mapping>

<servlet-name>HttpClusterServlet</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>HttpClusterServlet</servlet-name>

<url-pattern>*.jsp</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>HttpClusterServlet</servlet-name>

<url-pattern>*.jspf</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>HttpClusterServlet</servlet-name>

<url-pattern>*.htm</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>HttpClusterServlet</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

</web-app>

<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN" "http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">

<weblogic-web-app>

<!-- 配置访问项目的前缀 -->

<context-root>/项目URL</context-root>

</weblogic-web-app>

weblogic集群之Session共享复制&超时设置

一、超时设置

1 web.xml
设置WEB应用程序描述符web.xml里的<session-timeout>元素。这个值以分钟为单位,并覆盖weblogic.xml中的TimeoutSecs属性

<session-config>
<session-timeout>24</session-timeout>
</session-config>

此例表示Session将在24分钟后过期

当<session-timeout>设置为-2,表示将使用在weblogic.xml中设置的TimeoutSecs这个属性值。

当<session-timeout>设置为-1,表示Session将永不过期,而忽略在weblogic.xml中设置的TimeoutSecs属性值。

该属性值可以通过console控制台来设置

2 weblogic.xml

设置WebLogic特有部署描述符weblogic.xml的<session-descriptor>元素的

TimeoutSecs属性。这个值以秒为单位

<session-descriptor>
<session-param>
<param-name>TimeoutSecs</param-name>
<param-value>2600</param-value>
</session-param>
</session-descriptor>

默认值是2600秒

二、共享设置

建立 weblogic.xml文件放置在WEB应用程序的WEB-INF下,以实现SESSION共享,内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/90">

<session-descriptor>

<debug-enabled>true</debug-enabled>

<persistent-store-type>replicated</persistent-store-type>

<sharing-enabled>true</sharing-enabled>

</session-descriptor>

<context-root>/</context-root> <!-- 这是什么?和代理项目中的<context-root>有何区别? -->

</weblogic-web-app>

三、超时和共享组合设置

<?xml version="1.0" encoding="UTF-8"?>

<weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/90">

<session-descriptor>

<debug-enabled>true</debug-enabled>

<session-param>
<param-name>TimeoutSecs</param-name>
<param-value>2600</param-value>
</session-param>

<persistent-store-type>replicated</persistent-store-type>

<sharing-enabled>true</sharing-enabled>

</session-descriptor>

<context-root>/</context-root> <!-- 这是什么?和代理项目中的<context-root>有何区别? -->

</weblogic-web-app>

这样能使session复制功能的实现,从而实现故障转移

关于密码设置与修改

在启动WebLogic的时候如果没有提示输入密码,则说明密码已经存储到以下文件中:

...\Oracle\Middleware\Oracle_Home\user_projects\domains\base_domain\servers\AdminServer\security\boot.properties

如果想在启动WebLogic的时候不用输入密码,则创建上述文件,输入:

username=用户名

password=密码

启动WebLogic后,该文件的用户密码信息会被加密

如何在控制台中修改密码

域结构 -> 安全领域 -> 点击领域名称(如myrealm) -> 用户和组 -> 点击创建域时设置的用户名 -> 口令

修改密码后,如果在AdminServer中有security目录则要删除。

serverstatus -all -username admin -password admin 查看所有服务器状态

概念

Servlet是用java语言开发的动态网页技术。

1)实现了Servlet接口的java类,就是一个Servlet。

2)我们写Servlet一般继承HttpServlet类(为了创建一个基于http协议的Servlet程序),HttpServlet类继承自GenericServlet类,GenericServlet实现了Servlet接口。

3)Servlet程序由Tomcat服务器运行!

Servlet开发步骤(记事本)

步骤一:编写一个实现Servlet接口或继承HttpServlet(推荐)的Java类HelloServlet.java,重写接口或父类的方法

步骤三:新建web.xml,内容如下

package web;

import java.io.IOException;import java.io.PrintWriter;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {

@Override

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.getWriter().write("<h1>Hello Servlet</h1>");

}

}

<?xml version="1.0" encoding="UTF-8"?>

<web-app> 

<!-- Servlet配置 -->

<servlet>

<!-- Servlet名称自定义一般写类名 -->

<servlet-name>HelloServlet</servlet-name>

<!-- Servlet类的全名包名+类名 -->

<servlet-class>web.HelloServlet</servlet-class> </servlet>

<!—Servlet映射配置 -->

<servlet-mapping>

<!-- Servlet名称,与上面的Servlet名称保持一致 -->

<servlet-name>HelloServlet</servlet-name>

<!-- 外部访问名称 -->

<url-pattern>/HelloServlet</url-pattern>

</servlet-mapping>

 </web-app>

步骤二:使用javac命令编译源文件为字节码文件:

步骤四:按下图所示结构将编译完的组件打包:

在HelloServlet.java文件所在的位置,输入如下命令(假设tomcat7.0在c:\java):

javac HelloServlet.java -cp c:\java\tomcat7.0\lib\servlet-api.jar

出现HelloServlet.class的文件,代表编译成功。

firstweb

|---WEB-INF

|---classes

| |---web

| |--- HelloServlet.class

|---lib

|---web.xml

步骤五:部署:将整个firstweb文件夹拷贝到c:\java\tomcat7.0\webapps

步骤六:启动Tomcat并访问Servlet

启动Tomcat,成功后浏览器访问http://localhost:8080/firstweb/HelloServlet,得到第一个Web应用程序的运行结果。

Servlet开发步骤(Eclipse)

  1. 新建web项目,生成web.xml文件,导包或添加Tomcat运行环境。
  2. 在src/main/java/下写Servlet,在src/main/webapp/下写HTML。
  3. 编写继承HttpServlet类(该父类实现Servlet接口)或实现Servlet接口的Java类,重写其方法。
  4. 配置web.xml文件:src/main/webapp/WEB-INF/web.xml
  5. Servers窗口 → 右击服务 → Add and Remove… → 添加项目 → Finish → 启动服务 → 浏览器输入 http://localhost:8080/项目名/url-pattern名 即可访问该web应用程序
  6. 常见错误及解决方法:
    1. 404错误:根据请求地址找不到对应资源。原因:地址有误/servlet-name不一致/部署位置或结构有误
    2. 405错误:服务器找不到service方法。解决方法:检查service方法是否存在,签名(方法名、参数、返回值、异常类型)是否与覆盖的父类中的方法一致。
    3. 500错误:执行service方法出现错误。原因:Servlet未继承HttpServlet类或实现Servlet接口/servlet-class有误/service方法抛异常

HTTP协议

含义:HyperText Transfer Protocol超文本传输协议,是对浏览器和服务器之间数据传输格式的规范。

注:http协议是在tcp/ip协议的基础上封装的,tcp/ip协议关注的是客户端与服务器之间数据传输是否成功。

HTTP协议的通信过程:浏览器建立连接发送请求 → 服务器接收请求发送响应 → 浏览器接收响应关闭连接。

HTTP协议特点:无状态协议。一次请求对应一次连接,需要时建立连接,结束后立即断开连接。

版本区别:1.0版在一次连接中只能发出一次请求;1.1版在一次连接中可以发出多次请求,效率更高!

用telnet远程登录工具发送请求

开启:控制面板 → 程序和功能 → 打开或关闭Windows功能 → 勾选“Telnet客户端” → 确定!

使用:使用cmd命令。

  1. 输入:telnet 远程主机ip 远程主机端口,如:telnet localhost 8080
  2. 按 Ctrl + ] 组合键激活回显功能。
  3. 再按Enter进入界面。
  4. 输入请求,如:

GET /servlet2/HelloServlet HTTP/1.1

Host: localhost:8100

  1. 得到响应内容。

注意:10秒无操作将自动退出。

HTTP协议的数据格式

请求头包括请求行、消息头和实体内容

Request Headers --请求头:

GET /servlet2/HelloServlet HTTP/1.1(必须) --请求行(请求方式、请求资源URI、协议类型/版本)

Host: localhost:8100(必须)(目标主机和端口) --消息头(若干键值对)

If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(浏览器缓存的最后修改时间)

Referer: http://www.it315.org/index.jsp (当前请求来自于哪里(可用于防止非法请求))

Connection: keep-alive(浏览器和服务器的连接状态。close:关闭;keep-alive:保持连接)

Cookie:name=eric(浏览器保存的cookie数据)

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)(浏览器类型)Chrome/55.0.2883.87 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8(浏览器接受的数据类型)

Accept-Encoding: gzip, deflate, sdch, br(浏览器接受的数据压缩格式)

Accept-Language: zh-CN,zh;q=0.8(浏览器接受的语言)

Date: Tue, 11 Jul 2000 18:23:51 GMT(请求发出的时间)

--空行

--实体内容(POST时为表单数据),此例为空

响应头包括状态行、消息头和实体内容

Response Headers --响应头:

HTTP/1.1 302 OK(必须) --状态行(协议类型/版本、状态码、状态描述)

Server: apache tomcat (服务器的类型) --消息头(若干键值对)

Location: http://www.it315.org/index.jsp(重定向的地址。结合302状态使用完成重定向的效果)

Content-Encoding: gzip(服务器发送给浏览器的数据压缩格式)

Content-Length: 80(服务器发送给浏览器的数据长度)

Content-Language: zh-cn(服务器支持语言)

Content-Type: text/html; charset=utf-8(服务器发送给浏览器的数据类型和数据编码格式)

Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务器资源的最后修改时间)

Refresh: 1;url=http://www.it315.org(定时刷新或每隔n秒跳转资源)

Content-Disposition: attachment; filename=aaa.zip(以下载方式打开资源)

Transfer-Encoding: chunked            

Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务器发送给浏览器的cookie数据)

Expires: -1(通知浏览器不使用缓存)

Cache-Control: no-cache(通知浏览器不使用缓存)

Pragma: no-cache (通知浏览器不使用缓存)

Connection: close/Keep-Alive(连接状态,close:关闭;keep-alive:保持连接)

Date: Tue, 11 Jul 2000 18:23:51 GMT(响应发出的时间)

--空行

--实体内容(响应数据),此例为空

常见状态码

状态码

常用

含义

100~199

表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程

200~299

200

表示成功接收请求并已完成整个处理过程

300~399

302

为完成请求,客户需进一步细化请求。例如,请求的资源已经移动一个新地址,常用302、307和304

400~499

404

客户端请求有错误

500~599

500

服务器端出现错误

请求方式GET和POST的区别

传参方式

action参数处理

传输量

使用场景

发生场景

GET

通过请求行的 URI传参,地址栏URL中可见,隐私性差,不适合传敏感数据。

action值中若带参数列表会被忽视。

数据传输量小,不能大于2KB。

不会改变服务器数据和状态(幂等性/安全方法)。重复使用GET请求页面时,浏览器会使用缓存处理后续请求,响应速度更快。查询时用GET

如下情况中浏览器会发送GET类型的请求:在地址栏输入地址、点击页面中链接、表单默认提交方式。

POST

通过实体内容传参,URL中不可见,隐私性好,适合传敏感数据。

action值中若带参数列表不会被忽视。

数据量不限。

POST是不安全的方法,会改变服务器数据或状态(更新数据、上传文件),应该用特殊的方式向用户展示,通常是按钮而不是链接,这样就可以使用户意识到可能要负的责任(例如一个按钮带来的资金交易。)保存、提交时用POST。

将表单的Method属性设置为POST时,浏览器会发送POST请求。

get方式提交的参数在地址栏的格式:例如:http://localhost:8080/firstweb/HelloServlet?name=Tom&gender=male

编码问题

原则上,服务器端处理GET和POST请求是没有分别的。但由于数据通过不同的方法编码,需要有不同的解码机制。所以,方法变化将导致处理请求的代码变化。比如对于CGI,处理GET时通过环境变量获得参数,处理POST请求时则通过标准输入(stdin) 获得数据。

提交数据时,Form提交的第一步是创建数据集,并根据ENCTYPE对数据集进行编码。ENCTYPE有两个值:multipart/form-data,application/x-www-form-urlencoded(默认值),前者可同时用于GET、POST,后者只用于POST。进行数据传输时,对于GET方法,数据集使用content type application/x-www-form-urlencoded编码并附在URL后面,在这种模式下,数据严格限制为ASCII码;对于POST,使用content-type编码字符集并将其构造成消息发送服务器端时,服务器端通过标准输入(stdin) 获得数据。

HttpServlet的被Servlet所重写的service方法和doXXX方法之间的关系

service方法是程序的入口(类似于main方法)。我们的代码逻辑就在这个方法被调用到。在HttpServlet的service方法中,根据不同请求方式调用了不同的doXXX方法,所以我们在开发Servlet的时候,就不需要重写service方法,而是重写doXXX方法。因为get和post是最常用的的两种请求方式,所以一般只需要覆盖doGet和doPost方法即可!

Servlet的线程并发问题

Servlet在Tomcat服务器中,是单实例多线程的

开发线程安全的Servlet建议

1)尽量不要使用成员变量,或者静态成员变量。

2)如果必须要使用成员变量,就给使用了成员变量的代码块加同步锁,加锁的代码块的范围尽量缩小,因为有可能影响程序并发效率。

Servlet的url-pattern匹配规则

首先需要明确几容易混淆的规则:

1)Servlet容器中的匹配规则既不是简单的通配,也不是正则表达式,而是特定的规则。所以不要用通配符或者正则表达式的匹配规则来匹配。

2)Servlet 2.5开始,一个servlet可以使用多个url-pattern规则(多个URL可使用同一个Servlet处理)。

3)当Servlet容器接收到浏览器发起的一个url请求后,容器会用url减去当前应用的上下文路径,以剩余的字符串作为servlet映射,假如url是http://localhost:8080/appDemo/index.html,其应用上下文是appDemo,容器会将http://localhost:8080/appDemo去掉,用剩下的/index.html部分拿来做servlet的映射匹配

4)url-pattern映射匹配过程是有优先顺序的,而且当有一个servlet匹配成功以后,就不会去理会剩下的servlet了。

匹配方式

url-pattern示例

URL示例

精确匹配(以/开头,无*)

/hello

http://localhost:8080/myapp/hello

/first/hello

http://localhost:8080/myapp/first/hello

模糊匹配(以/开头,以/*号结尾)

/*

http://localhost:8080/myapp/任意路径

/first/*

http://localhost:8080/myapp/first/任意路径

后缀匹配(以*开头。常用后缀.do、.action)

*.后缀名

http://localhost:8080/myapp/任意路径.后缀名

默认匹配(/)

放行其他Servlet不处理的请求,主要是放行静态资源)

(Tomcat自带JspServlet处理并放行*.jsp和*.jspx请求)

http://localhost:8080/myapp/任意路径.jsp或.jspx后缀

重写默认匹配(/)

拦截其他Servlet不处理的请求,需配置静态资源映射

(Tomcat自带JspServlet处理并放行*.jsp和*.jspx请求)

/

注意

1)要么以斜杠开头,要么*号开头。非法的url-pattern例如: index.html

2)*号要么在开头,要么在结尾,不要夹在中间。非法的url-pattern例如: /myapp/*.html

3)/*拦截所有,一般只用于过滤器。

4)前三种匹配方式映射到动态网页,默认匹配方式映射到静态网页,重写默认匹配方式后映射到所有请求(可添加静态资源映射)。

5)默认映射路径斜杠/由Tomcat的web.xml定义并映射到DefaultServlet,用于放行所有其他Servlet都不处理的请求。

6)如果默认映射路径被重写到新的Servlet,会放行拦截所有其他Servlet都不处理的请求。解决方法:添加静态资源映射。

7)不管重不重写默认映射,*.jsp和*.jspx请求都不会被拦截,因为会由Tomcat的JspServlet处理。Tomcat的web.xml定义了两个Servlet及其映射,即DefaultServletJspServlet

8)匹配优先级(过程)优先精确匹配,其次模糊匹配,再次后缀匹配,最后默认映射路径(直接找文件),找不到则返回404错误。

(先找动态网页,找不到再找静态网页,再找不到报错)

9)SpringMVC的前端控制器如果是互联网项目建议重写默认映射并添加静态资源映射,如果是企业内部项目建议使用后缀映射。

Servlet如何访问数据库:使用JDBC技术访问数据库:

步骤一、将JDBC驱动(.jar文件)放到WEB-INF\lib下

原因:ClassLoader找到字节码文件,然后加载到JVM的方法区中,变成一个对象。Tomcat都有自己的类加载器,会去WEB-INF下面lib中找字节码文件。毕竟jar包中都是字节码文件。

步骤二、编写JDBC代码,需要注意异常的处理

HttpServletRequest 常用方法

获取请求行

获取请求方式:String request.getMethod();

获取请求资源:假定web应用名为news,浏览器请求路径为 http://localhost:8080/news/main/list.jsp

StringBuffer request.getRequestURL(); http://localhost:8080/news/main/list.jsp

String request.getRequestURI(); /news/main/list.jsp

request.getContextPath();  /news

request.getServletPath(); /main/list.jsp

request.getRealPath("/");  F:\Tomcat 6.0\webapps\news\test

获取HTTP协议/版本: String request.getProtocol();

获取消息头

获取单个消息头的值: String request.getHeader(String name);

获取多个消息头的值: Enumeration<String> String request.getHeaders(String name);

获取所有消息头名称: Enumeration<String> request.getHeaderNames(); 返回的是枚举(迭代器)

获取所有消息头信息:

Enumeration<String> e = request.getHeaderNames();

while (e.hasMoreElements()) {

String headerName = e.nextElement();

String headerValue = request.getHeader(headerName);

System.out.println(headerName + ": " + headerValue);

}

一些案例

  1. 利用User-Agent消息头获取用户浏览器类型:提示用户更换适合的浏览器,分析不同浏览器访问比率,根据不同浏览器执行不同代码等。
  2. 利用Referer消息头防止非法请求:

正常请求的流程:下载资源 -> 下载页面 -> 打开广告页面(下载链接) -> 开始下载

非法请求的处理:直接点击下载链接 -> 跳转广告页面(下载链接) -> 开始下载

非法请求的判定:referer==null || !referer.contains("/adv.html")

  1)直接访问下载资源(如地址栏直达):referer==null 只有从超链接过来的请求才有Referer请求头。

  2)不是从广告页面过来的请求(如盗链):!referer.contains("/adv.html")

String referer = res.getHeader("Referer");

if (referer == null || !referer.contains("/adv.html")) {

resp.getWriter().write("你当前的下载请求是非法请求,请重新下载!<a href='/download/adv.html'>重新下载</a>");

} else {

resp.getWriter().write("资源正在下载.....");

}

获取实体内容:(注意中文编解码问题)

ServletInputStream request.getInputStream();

BufferedReader request.getReader();

注:URL编解码类:URLEncoder和URLDecoder编解码方法:

static String java.net.URLEncoder.encode(String s, String enc) throws UnsupportedEncodingException;

static String java.net.URLDecoder.decode(String s, String enc) throws UnsupportedEncodingException;

示例:

InputStream in = request.getInputStream();

byte[] buf = new byte[1024];

int len = 0;

while ((len = in.read(buf)) != -1) {

String body = new String(buf, 0, len);

str = URLDecoder.decode(str, "utf-8"); //若有中文URL,用URL依页面指定字符集解码

System.out.print(body); //不要换行

}

获取查询字符串

获取GET方式提交的查询字符串:String request.getQueryString();

获取POST方式提交的查询字符串:同获取实体内容

其他

getPathInfo(); 获取请求URL中的额外路径信息(URL中的Servlet之后和查询参数之前的内容,以“/”开头)

getRemoteAddr(); 获取发出请求的客户机的IP地址

getRemoteHost(); 获取发出请求的客户机的完整主机名

getRemotePort(); 获取客户机所使用的网络端口号

getLocalAddr(); 获取WEB服务器的IP地址

getLocalName(); 获取WEB服务器的主机名

获取表单数据

String request.getParameter(String paramName); 获取指定单个参数值。若参数名写错,返回null

String[] request.getParameterValues(String paramName); 获取指定一组同名参数值(如复选框)。若用户未勾选,返回null。

Enumeration<String> request.getParameterNames(); 获取所有参数名(返回枚举,参考“获取所有消息头名称”)

Map<String, String[]> request.getParameterMap(); 获取所有参数对象(名和值)

域操作

request.setAttribute(String name,Object obj); 绑定数据

request.getAttribute(); 获取绑定的数据

request.removeAttribute(); 移除绑定的数据

HttpServletResponse 常用方法

修改状态码

void response.setStatus(404); (将只发送错误码)

void response.sendError(404); (将发送错误码+错误页面)

修改消息头

void response.setHeader(String name, String value);

修改实体内容

response.getWriter().write("Hello World!"); 用于传输文字(字符流

response.getOutputStream().write("Hello Java!".getBytes()); 用于传输文件(字节流),文字亦可

设置返回的数据类型、字符编码和浏览器解码方式(数据类型可在Tomcat的web.xml中文件找)

res.setContentType("text/html;charset=utf-8");(此种编码设置会通过响应头通知浏览器)

设置返回的字符编码(不推荐,此种编码设置不会通过响应头通知浏览器)

res.setCharacterEncoding("utf-8");

案例

  1. 302+location实现重定向的另一种写法:
  2. 使用refresh消息头实现定时跳转/定时刷新:
  3. res.setContentType("text/html;charset=utf-8");的另一种写法:
  4. 使用content-disposition消息头告诉浏览器以下载方式打开图片:

response.setStatus(302);

response.setHeader("location", "/servlet/NewFile.html");

response.getWriter().write("注册成功!3秒后自动转到主页");

response.setHeader("refresh", "3;/servlet/NewFile.html");

response.setHeader("content-type", "text/html;charset=utf-8");

File file = new File("E:/图片/风景/A.JPG");

response.setHeader("content-disposition", "attachment;filename=" + file.getName());

FileInputStream fis = new FileInputStream(file);

OutputStream out = response.getOutputStream();

byte[] buf = new byte[1024];

int len = 0;

while ((len = fis.read(buf)) != -1) {

out.write(buf, 0, len);

}

fis.close();

转发和重定向

含义

浏览器地址栏

执行者

范围

浏览器请求次数

request和response

使用场景

转发

转发是一个请求未处理完,通知容器转交另外一个组件继续处理。

浏览器不知转发,地址栏不变

服务器

转发地址必须是内部地址,只能转发到当前web应用内部的组件或资源

一次请求,。

组件间共用同一对request和response对象

重定向

重定向是一个请求已经处理完,再处理另外一个请求。

浏览器收到302状态码和location,地址栏变为重定向的地址

浏览器

重定向地址可以为外部地址,可以重定向到当前web应用,其他web应用,甚至其他站点的组件或资源

两次请求,

两个组件前后各用一对不同的request和response对象,存在时期不同,不能也无法共享。(可通过ServletContext对象共享)

增删改后重定向至修改后的数据展示页面(防止表单重复提交)

转发步骤

  1. 绑定数据到request对象:request.setAttribute(String name,Object obj);
  2. 获得转发器和实现转发:request.getRequestDispatcher(String uri).forward(request,response);

重定向步骤

  1. 可以绑定数据到ServletContext对象:getServletContext().setAttribute(String name, Object obj);
  2. 实现重定向:response.sendRedirect(String url);

注意:不管转发还是重定向,代码后面若还有其他代码则会继续执行,除非有异常。

如何保证Servlet线程安全使用synchronized加锁

package web;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class SomeServlet extends HttpServlet {

private int count = 0;

@Override

public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

synchronized (this) {

count++;

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ":" + count);

}

}

}

ServletConfig

ServletConfig常用方法

String getInitParameter(String name);  根据指定的初始化参数名获取该初始化参数的值

Enumeration getInitParameterNames(); 获取所有初始化参数的名(返回枚举)

ServletContext getServletContext(); 获取Servlet上下文

String getServletName(); 获取Servlet名

注:在当前Servlet获取ServletConfig的方法:ServletConfig config = getServletConfig();

ServletConfig配置对象用于封装Servlet的初始化参数和为Servlet传入ServletContext。一个ServletConfig封装一个Servlet的配置信息。

Servlet的初始化参数在Servlet配置文件web.xml中通过<servlet>的子标签<init-param>进行配置:

<servlet>

<servlet-name>ConfigDemo</servlet-name>

<servlet-class>gz.itcast.f_config.ConfigDemo</servlet-class>

<!—服务器启动时自动加载servlet -->

<load-on-startup>1</load-on-startup>

<!-- servlet的初始化参数 -->

<init-param>

<param-name>path</param-name>

<param-value>e:/aaa.txt</param-value>

</init-param>

</servlet>

在Servlet中读取web.xml中设置的初始化参数:

String path = getServletConfig().getInitParameter("path");

ServletConfig对象还可让Servlet接受一个ServletContext对象。

ServletContext

什么是ServletContext(上下文

WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用的环境信息。ServletContext对象作为ServletConfig对象的成员变量随ServletConfig传入Servlet中(通过带参的init方法!)。该应用任何组件在任何时候都可以访问该对象,所以Servlet上下文具有唯一性。

获取Servlet上下文(四种)

• GenericeServlet(HttpServlet)提供的 getServletContext()

• ServletConfig提供的 getServletContext() (根本方式)

• HttpSession提供的 getServletContext()

• FilterConfig提供的 getServletContext()

根本方式:this.getServletConfig().getServletContext(); 通过ServletConfig对象来获取到ServletContext对象!

简化写法:getServletContext();

ServletContext对象:服务器启动的时候创建

ServletConfig对象:调用init方法之前创建,先于ServletContext对象!

伪代码:

public ServletCofig{

ServletContext context;

public ServletConfig(context){

this.context=context;

}

public ServetContxt getServletContext(){

return context;

}

}

ServletConfig config = new ServletConfig(context);

public MyServlet extends HttpSevlet{

publlic init(ServletConfig config){

SevletContext context= config. getServletContext();

}

}

ServletContext常用方法

  1. 获取web的上下文路径(应用名的路径):String getContextPath(); 上下文路径是指应用名(前面加个“/”),不是项目名。
    eclipse更改应用名的方法:项目右键 → Properties → Web Project Settings → 修改Context root。
    简化写法:String request.getContextPath()
  2. 获取全局参数:
    String getInitParameter(String name); 根据指定参数名获取该参数值
    Enumeration getInitParameterNames(); 获取所有参数名(返回枚举)
    web.xml中全局参数的配置:
  3. 域操作:
    void setAttribute(String name, Object object);
    Object getAttribute(String name);
    void removeAttribute(String name);
    注:域对象是指在不同资源(Servlet/JSP)之间共享数据的对象。
    Servlet有三个域对象:ServletContext、HttpServletRequest、HttpSession。
    JSP有一个域对象:PageContext对象。
    例:在两个Servlet中实现跨Servlet的数据共享:
    SomeServlet中:getServletContext().setAttribute("name", "Lisa");
    OtherServlet中:String name = (String) getServletContext().getAttribute("name");
  4. 获取转发:
    RquestDispatcher getRequestDispatcher(String path); (只能写绝对路径,不建议使用)
    请求转发:context.getRequestDispatcher("路径").forward(request,response);
    请求转发简化写法:request.getRequestDispatcher("路径").forward(request,response);
  5. 读取web项目的资源文件
    String getRealPath(String path); 获取资源文件的真实路径
    InputStream getResourceAsStream(String path); 获取资源文件的输入流
    java.net.URL getResource(String path); 获取资源文件的URL(日后研究)
    读取web项目资源文件不要用相对目录。因为相对路径的当前目录是Java命令运行的目录,各钟服务器启动Java命令的目录不同(可用 System.out.println(new File(".").getAbsolutePath()); 查看当前目录)。

  <context-param>

  <param-name>path</param-name>

  <param-value>e:/aaa.txt</param-value>

  </context-param>

String path = getServletContext().getRealPath("/WEB-INF/classes/haha.properties");

FileInputStream f = new FileInputStream(path);

Properties p = new Properties();

p.load(f);

System.out.println(p.getProperty("name"));

System.out.println(p.getProperty("password"));

InputStream in = getServletContext().getResourceAsStream("/WEB-INF/classes/haha.properties");

// FileInputStream f = new FileInputStream(path);

Properties p = new Properties();

// p.load(f);

p.load(in);

System.out.println(p.getProperty("name"));

System.out.println(p.getProperty("password"));

Java Web应用的生命周期

初始:

1)把web.xml加载到内存中

2)创建一个ServletContext对象

3)初始化所有Filter

4)初始化需要在启动时初始化的Servlet

运行:

对于不需要再启动时初始化的Servlet,在客户端第一次请求该Servlet时初始化该Servlet

销毁:

1)销毁Servlet

2)销毁Filter

3)销毁所有应用相关对象,如ServletContext

Tomcat Server处理一个http请求的过程

浏览器发送请求

假设为http://localhost:8080/hello/index.jsp

客户端电脑解析并发送请求

客户端电脑从本地hosts文件查询域名和ip的映射,找不到则联网到运营商的DNS服务器查询,查到后向目标ip发送请求。

(localhost为本机域名,直接发送请求给本机,本机又当客户端又当服务端)

服务器电脑接收并解析请求

1)服务器电脑接收请求,请求被分派到8080端口,被在那里侦听的Tomcat的Coyote HTTP/1.1 Connector获得

2)Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应

3)Engine把该请求交给name="localhost"的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)

4)该Host把该请求交给path="/hello"的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)

5)该Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet(匹配url-pattern找servlet-name,进而找servlet-class)

6)Context根据找到的servlet-class寻找目标servlet对象,如未找到,则通过反射创建该servlet对象

7)Context构造HttpServletRequest和HttpServletResponse对象,作为参数调用目标servlet对象的service方法(doGet或doPost方法)

目标Servlet处理请求并发送响应

8)目标servlet的service方法读取请求信息,处理完毕后写入响应信息

9)Context把执行完了之后的HttpServletResponse对象返回给Host

10)Host把HttpServletResponse对象返回给Engine

11)Engine把HttpServletResponse对象返回给Connector

12)Connector把HttpServletResponse对象返回给客户browser

Service何时销毁

WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

web.xml的配置中<context-param>配置作用
1.启动一个WEB项目的时候,容器(如Tomcat)会去读它的配置文件web.xml中的节点: <listener>和<context-param>

2.紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文.

3.容器将<context-param>转化为键值对,并交给ServletContext.

4.容器创建<listener>中的类实例,即创建监听.

5.在监听中有contextInitialized(ServletContextEvent args)初始化方法,在该方法中获得ServletContext ServletContextEvent.getServletContext();
context-param的值 = ServletContext.getInitParameter("context-param的键");

6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.
换句话说,这个时候,你对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行.

7.举例.你可能想在项目启动之前就打开数据库.
那么这里就可以在<context-param>中设置数据库的连接方式,在监听类中初始化数据库的连接.

8.这个监听是自己写的一个类,除了初始化方法,它还有销毁方法.用于关闭应用前释放资源.比如说数据库连接的关闭.

Servlet的生命周期(四个阶段)

阶段

容器所调用方法

调用次数(一个周期内)

何时调用

方法作用

实例化

构造方法

调用一次(Servlet是单例的)

默认接收第一次请求时调用

创建Servlet对象

初始化

init方法

调用一次

初始化参数

服务

service()方法

多次,请求一次调用一次

处理业务逻辑,输出响应

销毁

destroy()方法

调用一次

容器停止或重启时

销毁Servlet

阶段一、实例化(容器调用默认无参构造方法创建Servlet)

默认在服务器收到对该Servlet请求之后创建该Servlet,亦可在服务器启动时自动加载:

在<servlet>标签内追加子标签:

<load-on-startup>1</load-on-startup>

正数值越小,优先级越高,应用启动时越先被创建。小于0或未指定,则请求时再创建。

阶段二、初始化(容器调用init方法初始化Servlet)

GenericServlet类两个init方法作用

带参的init(ServletConfig config)方法是Servlet的初始化方法,该方法首先传入ServletConfig对象给Servlet,然后调用无参的init()方法,以供开发者重写额外的初始化逻辑。如需增加初始化逻辑,建议重写无参的init方法,不要重写带参的init方法,以免忘记传入ServletConfig对象。

阶段三、服务(容器调用service()方法运行Servlet)

Servlet初始化以后就可以接收请求发送响应了!每个请求封装成ServletRequest对象,并与ServletResponse对象以参数的形式传给Servlet实现类的service()实现方法。

阶段四、销毁(容器调用destroy()方法销毁Servlet)

Servlet容器停止或重启时销毁之。

用伪代码演示tomcat服务器如何调用四个方法

1)通过反射,创建HelloServlet的对象

1.1 得到HelloServlet的CLass对象

Class clazz = Class.forName("gz.itcast.a_servlet.HelloServlet ")

1.2 通过class对象调用构造方法

Object obj  =  clazz.newInstance();  调用无参的构造方法。  --1)构造方法被调用

2)通过反射,调用init方法

2.1 得到init方法对象

Method m = clazz.getDeclaraeMethod("init",ServletConfig.class);

2.2 调用方法

m.invoke(obj,config);                      --2)init方法被调用

3)通过反射,调用service方法

3.1 得到service方法对象

Method m = clazz.getDeclareMethod("service",HttpServletRequest.class,HttpServletResponse.class);

3.2 调用方法

m.invoke(obj,request,response);          --3)service方法被调用

4)通过反射,调用destroy方法

4.1 得到destroy方法对象

Method m= clazz.getDeclareMethod('destroy",null);

4.2调用方法

m.invoke(obj,null);                --4)destroy方法被调用

使用画图演示四个生命周期

Servlet接口、GenericServlet抽象类、HttpServlet实现类三者关系

在ServletAPI中最重要的是Servlet接口,所有Servlet都会直接或间接的与该接口发生联系,或是直接实现该接口,或间接继承自实现了该接口的类。

该接口包括以下四个方法:

init(ServletConfig config)

service(ServletRequest req,ServletResponse res)

destroy( )

在最开始制定Servlet规范时,设计者希望这套规范能够支持多种协议的组件开发,所以Servlet接口是最重要的一个接口。虽然我们写的程序中编写的Servlet都是继承自HttpServlet,但本质上都是对该接口的实现,因为HttpServlet就是针对Servlet这个接口的一个抽象的实现类。可以理解为HttpServlet是支持HTTP协议的分支的一部分。设计Servlet接口中的service方法时,也是希望该方法能够处理多种协议下的请求及响应,所以参数类型是ServletRequest,而在HttpServlet这个支持HTTP协议的分支中,service方法的参数则变成了HttpServletRequest和HttpServletResponse,这两个类分别继承于ServletRequest和ServletResponse,也就是对这两个类的一个具体协议的包装,区别是增加了很多与HTTP协议相关的使用API。

制定的这种规范在实际使用中发现,并不会扩展为HTTP协议之外,所以有了过度设计的缺陷,也为在编写HTTP协议的Web应用时添加了一些不必要的操作。

Servlet API中另一个重要的类就是GenericServlet这个抽象类,它对Servlet接口中的部分方法(init和destroy)添加了实现,使得开发时只需要考虑针对service方法的业务实现即可。

HttpServlet又是在继承GenericServlet的基础上进一步的扩展,一个是public voidinit(ServletConfig config),另一个是 public void init()。他们有如下的关系: init(ServletConfig config)方法由Tomcat自动调用,它读取web工程下的web.xml,将读取的信息打包传给此参数,此方法的参数同时将接收的信息传递给GenericServlet类中的成员变量config,同时调用init()。以后程序员想重写init方法可以选择init(ServletConfig config)或者init(),但是选择init(ServletConfig config)势必会覆盖此方法已实现的内容,没有为config变量赋值,此后若是调用getServletConfig()方法返回config时会产生空指针异常的,所以想重写init(ServletConfig config)方法,必须在方法体中第一句写上 super.init(config),为了防止程序员忘记重写super.init(config)方法sun公司自动为用户生成一个public void init()的方法。GenericServlet具体的定义如下所示

GenericServlet{

       ServletConfig config;

       public void init(){}   //此方法什么也没做,可以说是为编程人员预留的接口

      public void init(ServletConfig config)

      {

          this.config=config;

          this.init();

      }

     getServletConfig()

     {

          return config;

     }

}

为什么需要状态管理

HTTP协议是无状态协议,一次请求对应一次响应,响应结束立即断开连接,同一个用户的不同请求对于服务器端来讲并不会认为这两个请求有什么关联性,并不会以此区分不同的客户端。但实际情况中还是需要服务器端能够区分不同的客户端以及记录与客户端相关的一些数据,所以状态管理能够做到不同客户端的身份识别。

什么是状态管理

将客户端与服务器之间多次交互当做一个整体来看待,并且将多次交互中涉及的数据保存下来,提供给后续的交互进行数据的管理即状态管理。(管理浏览器与服务器之间的交互数据

状态管理两种常见模式

javax.servlet.http.Cookie

过程

数据存放位置

数据类型

安全性

Cookie

1)服务器端创建Cookie数据。2)服务器通过set-cookie响应头发送给浏览器保存。3)浏览器下次访问服务器时,会携带cookie数据通过cookie请求头发送给服务器处理,处理完后再从2)循环直至cookie失效。

数据放在浏览器端(默认内存中,可设置到硬盘)

只能string,而且大小有限制

不安全

Session(会话)

1)服务器端创建Session数据,并为该组数据编号(JSESSIONID)。2)服务器只将编号通过set-cookie响应头发给浏览器保存。3)当浏览器再次请求时只需通过cookie请求头发送该编号,服务器按编号搜索对应的数据,处理完后再从2)循环直至cookie或session失效。

数据放在服务器端(内存中),占用服务器资源

任意类型,没有大小限制。

安全

Cookie的原理一旦创建,自动交互

如何创建和发送Cookie

使用Cookie的构造方法创建cookie对象(名值对):

Cookie cookie = new Cookie(String name, String value); 

将cookie对象添加到响应消息头(set-cookie),将自动发送给浏览器保存,若有同name且同path的cookie,之前的value会被覆盖:

response.addCookie(cookie); // response.setHeader("set-cookie", "name=value");

        Cookie c1 = new Cookie("username","Lisa");        

        Cookie c2 = new Cookie("city","NewYork");

        response.addCookie(c1); //添加后会自动发送给浏览器

        response.addCookie(c2); //添加后会自动发送给浏览器

如何接收和查询Cookie

浏览器带着cookie访问服务器(请求头是cookie),服务器接收cookie信息

Cookie[] request.getCookies(); // request.getHeader("cookie");

String cookie.getName();

String cookie.getValue();

Cookie[] cookies = request.getCookies(); //返回多个cookies

if (cookies != null) {

for (Cookie cookie : cookies) {

String name = cookie.getName();

String value = cookie.getValue();

System.out.println(name + "=" + value);

}

} else {

System.out.println("没有cookie!");

}

如何修改Cookie的值

cookie.setValue(String newValue); 修改此cookie的值。

Cookie[] cookies =  request.getCookies();

if(cookies!=null){

        for(Cookie c : cookies){

            String cookieName = c.getName();

             if(cookieName.equals(“uname”)){

                 c.setValue(“Mark”);

                 response.addCookie( c );

         }

}

Cookie的生存时间

默认情况下被浏览器保存在内存中,只要不关闭浏览器Cookie就会一直存在。

可设置过期时间使Cookie保存在硬盘上以延续更长时间:

cookie.setMaxAge(int seconds); 指定失效时长,参数seconds的单位为秒,但精度不是很高。

seconds > 0 :代表Cookie保存在缓存(硬盘上)的时长,从最后一次使用cookie开始计算。

seconds = 0 :删除此cookie。

seconds < 0 :默认值(-1),浏览器关闭后失效(Cookie保存在内存中)。

Cookie的有效路径

客户端发送Cookie的条件:只有符合路径规范的请求才会发送Cookie到服务器端。

Cookie的默认路径:客户端在接受Cookie时会为该Cookie记录一个默认路径,这个路径记录的是添加这个Cookie的Web组件的路径。如,当客户端向 http://localhost:8080/test/file/addCookie.jsp发送请求时创建了cookie,那么该cookie的路径就是 /test/file/,只有当访问的地址是Cookie的路径或者其子路径时,浏览器才发送Cookie到服务器端。

如何设置Cookie的路径:

cookie.setPath(String uri); 指定此cookie起作用的基础请求路径,默认为截取请求URL的值从开头截到最后斜杠。

Cookie中文编码和解码

Cookie只能保存ASCII字符,使用编码类URLEncoder和解码类URLDecoder的两个静态方法可以让Cookie存取中文:

static String URLEncoder.encode(String s, String enc);

static String URLDecoder.decode(String s, String enc);

Cookie c = new Cookie("city",URLEncoder.encode("北京","utf-8")); //存中文

String value = URLDecoder.decode(cookie.getValue(),"utf-8")); //取中文

Cookie的限制

  1. 可以通过修改设置被用户禁止。
  2. 内容限制:只能保存字符串(Session还可以保存对象),只能保存ASCII字符,长度最大4kb左右,
  3. 数量限制:浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie。
  4. 安全性很低,易被截取数据包获取,未加密情况下不要存放敏感数据。
  5. 当大量请求时,Cookie的传递会增加服务器负担。

什么是Session

服务器为不同的客户端在内存中创建了用于保存数据的Session对象,并将用于标识该对象的唯一编号(JSESSIONID)作为Cookie发回给与该对象对应的客户端。当浏览器再次发送请求时,JSESSIONID也会被发送过来,服务器凭借这个唯一JSESSIONID搜索与之对应的Session对象。在服务器端维护的这些用于保存与不同客户端交互时的数据的对象叫做Session。

Session的原理:

数据存储在服务器端,通过JSESSIONID在服务器中查询对应的session对象。

如何获得Session

若请求中无SID,则创建session;若请求中有SID,则根据SID查找session,但也存在找到找不到的可能:

HttpSession session = request.getSession(); 有则取无则建,必返回一session(无参默认为true。校验旧Session失败则创建新Session)

HttpSession session = request.getSession(false); 有则取无则返空,要防止空指针(校验旧Session失败则返回null)

使用第一种获取session对象的方法时,

flag = true:若无SID或按SID找不到session则创建新session,若有SID且找到session则返回此session。故flag为true时必返回一session对象。

flag = false:若无SID或按SID找不到session时则返回null,只有根据SID找到session时才会返回此session。

如何使用Session管理数据(任意类型)

void session.setAttribute(String name,Object obj); //添加数据

Object session.getAttribute(String name); //获取指定数据

void session.removeAttribute(String name); //移除指定数据

void invalidate(); //移除session,常用于登出操作(removeAttribute("用户名")?)

设置Session超时时间和JSESSIONID超时时间

Session默认超时时间为30分钟,此时间段内无相应请求则Session对象自动销毁。

JSESSIONID默认浏览器关闭后丢失。

设置Session超时时间

编程式(单个设置,单位为秒):void setMaxInactiveInterval(int seconds);

声明式(全部设置,单位为分钟):在web.xml中:

<session-config>

<session-timeout>30</session-timeout>

</session-config>

设置JSESSIONID超时时间:

/*设置JSESSIONID的时间,不会随着浏览器关闭而丢失!*/

Cookie c = new Cookie("JSESSIONID",session.getId());

c.setMaxAge(1*30*24*60*60);//1个月

response.addCookie(c);

Session登陆验证

Session既然区分不同的客户端,所以可以利用Session来实现对访问资源的保护。如可将资源划分为登录后才能访问。

使用Session实现验证的步骤如下:

步骤一、为Session对象绑定数据:

HttpSession s = request.getSession();

s.setAttribute("uname","Rose");

步骤二、读取Session对象中的绑定值,读取成功代表验证成功,读取失败则跳转回登录页面:

HttpSession s = request.getSession();

if(s.getAttribute(“uname”)==null){

        response.sendRedirect(“logIn.jsp”);

}else{

        //… …

}

Session优缺点

Session数据保存在服务器端,安全性高,能保存更多的数据类型和更多的数据量,

Session的安全性是以牺牲服务器资源为代价的,如果用户量过大,会严重影响服务器的性能。

浏览器禁用Cookie后的Session处理:URL重写

为什么要URL重写

若浏览器禁用Cookie,则SID无法保存,Session对象将不能再使用。为了在禁用Cookie后依然能使用Session,可通过URL重写来保存SID。

什么是URL重写

浏览器在访问服务器的某个地址时,在原有地址后追加SessionID。

如:原有地址的写法为http://localhost:8080/test/some

重写后的地址写法为http://localhost:8080/test/some;jsessionid=4E113CB3

如何实现URL重写

生成链接地址和表单提交时,使用如下代码:

<a href="<%=response.encodeURL(String url)>">链接地址</a>

如果是重定向,使用如下代码代替response.sendRedirect():

response.encodeRedirectURL(String url);

验证码设置步骤

绘制验证码图片不仅仅需要随机生成要绘制的内容,同时要配合Java中与绘图有关的一套API来完成。绘制API将画板、画笔、颜料、字体等都解释为对象,绘制的过程就是这些对象互相配合完成的。主要涉及Graphics、Font等类型。

绘制验证码图片

package web;

import java.awt.*;

import java.awt.image.BufferedImage;

import java.io.IOException;

import java.io.OutputStream;

import java.util.Random;

import javax.servlet.*;

import javax.servlet.http.*;

public class CheckcodeServlet extends HttpServlet {

    private int width = 80; //图片的宽度

    private int height = 30;//图片的高度

    public void service(HttpServletRequest request,

HttpServletResponse response)

                           throws ServletException, IOException {

        /*

         * 绘图

         */

        //step1,创建一个内存映像对象(画板)

        BufferedImage image = new BufferedImage(width,height,

                                              BufferedImage.TYPE_INT_RGB);

        //step2,获得画笔

        Graphics g = image.getGraphics();

        //step3,给笔上色

        Random r = new Random();

        g.setColor(new Color(r.nextInt(255), r.nextInt(255),r.nextInt(255)));

        //step4,给画板设置背景颜色

        g.fillRect(0, 0, width, height);

        //step5,绘制一个随机的字符串

        String number = r.nextInt(99999) + "";

        g.setColor(new Color(0,0,0));

        //new Font(字体,风格,大小)

        g.setFont(new Font(null,Font.ITALIC,24));

        g.drawString(number, 5, 25);

        //step6,加一些干扰线

        for(int i=0;i < 8;i++){

            g.setColor(new Color(r.nextInt(255),

                    r.nextInt(255),r.nextInt(255)));

            g.drawLine(r.nextInt(width),

                    r.nextInt(height), r.nextInt(width),

                    r.nextInt(height));

        }

        /*

         * 压缩图片并输出到客户端(浏览器)

         */

        response.setContentType("image/jpeg");

        OutputStream ops =response.getOutputStream();        

        javax.imageio.ImageIO.write(image, "jpeg", ops);

        ops.close();

    }

}

配置web.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.4"

    xmlns="http://java.sun.com/xml/ns/j2ee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>

<servlet-name>CheckcodeServlet</servlet-name>

<servlet-class>web.CheckcodeServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>CheckcodeServlet</servlet-name>

<url-pattern>/checkcode</url-pattern>

</servlet-mapping>

</web-app>

HTML页面中添加如下代码加入该图片

<img src="checkcode"/>

密码加密之摘要加密

摘要加密的特点

不适用密钥,使用摘要加密算法对明文进行加密之后会得到密文,无法反推出明文。

唯一性:不同的明文有不同的密文。

不可逆性:即使知道了摘要加密算法,也无法反推出明文。

如何实现摘要加密

public class MD5Util{

    private static void test1() throws NoSuchAlgorithmException{

        String str = "I love you";

        MessageDigest md = MessageDigest.getInstance("md5");

        //依据指定的加密算法进行加密

        byte[] buf = md.digest(str.getBytes());

        //因为字节数组不方便使用,所以,将其转换成一个字符串

        //BASE64Encoder的encode方法可以将任意的一个字节数组转换成一个字符串

        BASE64Encoder  base = new BASE64Encoder();

        String str2 = base.encode(buf);

        System.out.println(str2);

}

    public static String encrypt(String  origStr){

        MessageDigest md = MessageDigest.getInstance("md5");

        byte[] buf = md.digest(str.getBytes());

        BASE64Encoder  base = new BASE64Encoder();

        String str = base.encode(buf);

        return str;

    }

}

防止表单重复提交:使用session设置Token令牌

产生页面时,服务器为每次产生的Form分配唯一的随机标识号Token,隐藏在hidden表单中,同时在当前用户的Session中保存这个标识号。当提交表单时,服务器比较hidden和session中的标识号是否相同,相同则删除Session中Token并处理表单数据,否则服务器提示请勿重复提交或session已失效。

注意:恶意用户可利用这一性质,不断重复访问页面,以致Session中保存的标识号不断增多,最终严重消耗服务器内存。可以采用在Session中记录用户发帖的时间,然后通过一个时间间隔来限制用户连续发帖的数量来解决这一问题。

注意:对于查询操作,一定不要加token,防止攻击者通过查询操作获取token进行csrf攻击。但并不是这样攻击者就无法获得token,只是增大攻击成本而已。

另一种不安全的方法:第一次提交后把已经提交的信息写到cookie中,当第二次提交时,由于cookie已经有提交记录,因此第二次提交会失败。不过,cookie存储有个致命弱点,如果cookie被劫持(xss攻击很容易得到用户cookie),那么黑客将直接实现csrf攻击。

Jsp:Java Server Page(提供Java服务的页面)。

特点:既可以写html代码,也可以写java代码。jsp页面需要交给tomcat服务器运行。

JSP运行目录:%tomcat%/work,这个目录下存放jsp页面运行过程中产生的临时文件。

三大动态网页开发技术:PHP、ASP、JSP

jsp的生命周期

翻译:hello.jsp → hello_jsp.java

编译:hello_jsp.java → hello_jsp.class

构造方法:hello_jsp类

_jspInit方法

_jspService方法

_jspDestroy方法

运行原理

第一次访问JSP:翻译;编译、构造类、执行类中方法

第n次访问JSP(未修改JSP页面):执行类中的方法

修改了jsp页面后:重走第一次访问JSP的过程。

问题:运行的class类是什么类?

public final class _01_hello_jsp extends org.apache.jasper.runtime.HttpJspBase

public abstract class org.apache.jasper.runtime.HttpJspBase extends javax.servlet.http.HttpServlet implements javax.servlet.jsp.HttpJspPage

结论:jsp的源文件是一个servlet!jsp就是一个servlet!!!servlet的技术可以用在jsp上!

    1. 语法

语法

作用

备注

翻译

JSP模板

HTML代码

JSP页面的html代码就是模板

out.write("内容");

JSP表达式

<%= 变量或表达式 %>

输出变量或表达式的值

结尾不能用分号

out.write("内容");

JSP脚本

<% 语句 %>

执行java语句

结尾要有分号

拷贝为_jspServcice方法中的语句

JSP声明

<%! 成员变量或方法 %>

声明成员变量或方法

声明的变量是成员变量,声明的方法是成员方法(不能与已有方法重复)

拷贝为类中的成员变量和方法及代码块

JSP注释

<%-- 注释内容 --%>

忽略

HTML注释

<!-- 注释内容 -->

若有Java代码,会被执行

Java不认识HTML注释

out.write("内容");

JSP指令

<%@ 指令名 属性=值 %>

转译成Servlet时的格式控制

page之import翻译为Java之import。

page之pageEncoding翻译为Java之response.setContentType方法

注意:1)不要声明和翻译后的java文件相同的方法名 2)jsp脚本不能声明方法

time.jsp

<!-- JSP指令 -->

<%@page pageEncoding="utf-8"%>

<!doctype html>

<html>

<head>

<meta charset="utf-8">

<title>JSP页面中的Java代码类型</title>

</head>

<body>

<%-- jsp声明 --%>

<%!

public double pf(double d) {

return d * d;

}

%>

<ul>

<%-- jsp脚本 --%>

<%

for (int i = 0; i < 10; i++) {

%>

<%-- jsp表达式,无分号 --%>

<li><%=pf(Math.random())%></li>

<%-- jsp脚本 --%>

<%

}

%>

</ul>

<%-- JSP指令 --%>

<%@include file="time.jsp"%>

</body>

</html>

<%@

page pageEncoding="utf-8" 

contentType="text/html"

import="java.util.*,java.text.*"

%>

<%

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

String now = sdf.format(date);

%>

<p>当前系统时间是:<%=now%></p>

page指令:告诉服务器如何翻译jsp文件(要在页面置顶)

language="java" 设置翻译语言:(目前只有java)

<%@ page import=“java.util.*,java.sql.*“ %> 导包:(导多个包用逗号隔开或写成多条)

<%@ page pageEncoding=“UTF-8“ %> 设置翻译JSP文件的编码

<%@ page contentType=“text/html;charset=utf-8“ %> 设置Content-Type响应报头

session="false" 关闭session功能

buffer="8kb" 设置缓存区大小(默认8kb,可为none)

autoFlush 自动

isThreadSafe

isELIgnored="false" 是否忽略EL表达式

errorPage="/common/error.jsp" 指定jsp错误处理页面

isErrorPage="true" 使exception内置对象可用(exception对象可查询错误信息)

<!-- 配置全局的错误处理页面,通常配置404和500错误 -->

<error-page>

<error-code>404</error-code>

<location>/common/404.html</location>

</error-page>

<error-page>

<error-code>500</error-code>

<location>/common/500.jsp</location>

</error-page>

Include指令:用于引入其他页面文件内容(页面重用)(先合并再翻译)(源码引入,也叫静态引入)

<%@ include file=“header.html” %>

注意:建议目标文件格式为jspf(jsp flagments),其页面不要使用全局的html标签。

taglib指令:用于导入jsp标签库

隐含对象

类型

说明

request

HttpServletRequest

请求对象

response

HttpServletResponse

响应对象

config

ServletConfig

Servlet配置对象

application

ServletContext

Servlet上下文对象

session

HttpSession

会话对象

page

Object

代表JSP文件翻译后的Java类,即this

pageContext

PageContext

JSP页面上下文(自定义标签常用)

exception

Throwable

异常信息对象

out

JSPWriter

输出数据流对象,带缓存的PrintWriter

pageContext对象

1)通过pageContext对象获取其他8个内置对象

场景:使用自定义标签的时候

2)作为域对象使用

2.1 保存数据到page域

pageContext.setAttribute("name", Object); // PAGE_SCOPE

2.2 保存数据到其他域

pageContext.setAttribute("name", Object, PageContext.REQUEST_SCOPE);

pageContext.setAttribute("name", Object, PageContext.SESSION_SCOPE);

     pageContext.setAttribute("name", Object, PageContext.APPLICATION_SCOPE);

2.3 获取数据从page域

pageContext.getAttribute("name"); // PAGE_SCOPE

2.4 获取数据从其他域

      Object pageContext.getAttribute("name",PageContext.REQUEST_SCOPE);

     Object pageContext.getAttribute("name",PageContext.SESSION_SCOPE);

     Object pageContext.getAttribute("name",PageContext.APPLICATION_SCOPE);

2.5 移除域

pageContext.removeAttribute()

2.5 自动搜索四个域数据(搜索顺序从小到大:page域、request域、session域、application域、null)

pageContext.findAttribute("name");

out对象

常用方法:

out.wirter("内容"); 把内容写入缓存或把已满的写入PrintWriter

out.flush(); 刷新缓存

out.getBufferSize(); 获取当前缓冲区大小

out.getRemaining(); 获取当前缓冲区剩余大小

比较:1)PrintWrite类直接往浏览器写出内容;2)JspWriter类相当于带缓存的PrintWriter

当满足以下条件,缓存区内容会写入到PrintWriter中:缓存区已满、缓存区关闭(buffer="0kb")、手动刷新缓存、jsp页面执行完毕。

异常处理

编程式的异常处理:使用转发跳转到指定页面进行提示说明,程序级的异常宜用此法

在Servlet中

catch (Exception e) {

e.printStackTrace();

request.setAttribute("error_msg","系统繁忙,稍后重试");

request.getRequestDispatcher("error.jsp").forward(request, response);

}

声明式的异常处理:统一指定异常跳转的页面。系统级别的异常宜用此法

web.xml

<error-page>

<exception-type>java.lang.Exception</exception-type>

<location>/error.jsp</location><!—用绝对路径,项目名无需写 -->

</error-page>

或者

<error-page>

<error-code>404</error-code>

<location>/WEB-INF/error.jsp</location>

</error-page>

一般404错误(客户端错误)和500错误(服务器错误)包含了大多数错误

在Servlet中

catch (Exception e) {

e.printStackTrace();

//将异常抛给容器处理,异常类型要与web.xml中声明的一致

throw new ServletException(e);

}

EL(Expression Language)表达式用于替代jsp表达式。JSTL标签用于替代jsp脚本。EL可嵌入JSTL中。

使用EL表达式获取数据前目标数据一定要先放入域对象中!!!

执行过程:从pageContext、request、session、application中依次根据绑定名查找所绑定对象(getAttribute()),并调用其getXxx()方法,将返回值输出。若无绑定名或属性值,输出””;若无属性名。输出null。

语法

a)四个域中获取:${变量} (从小到大顺序:pageContext、request、session、application、空字符串(""))

等价于:<%=pageContext.findAttribute("变量")%>

b)指定域中获取:${域范围.绑定名.属性名}

域范围:pageScope、requestScope、sessionScope、applicationScope

获取普通字符串数据

${name} 等价于:<%=name %>

获取Bean对象数据

${student.name} 等价于:<%=student.getName() %>

${student[var]}(动态读取,var为变量)

动态读取示例

<%! User obj = new User(1, “胡萝卜”); %>

<% session.setAttribute(“user”, obj); %>

<% session.setAttribute(“var”, ”id”); %>

${sessionScope.user[sessionScope.var]} //输出“1”

<% session.setAttribute(“var”, ”name”); %>

${sessionScope.user[sessionScope.var]} //输出“胡萝卜”

获取数组数据

${arr[0] } 等价于:<%=arr[index] %>

获取List集合数据

${list[index] } 等价于:<%=list.get(index) %>

获取Map集合数据

${map[key] } 等价于:<%=map.get(key) %>

运算

算术运算: ${a+b} 整数相除值为小数

逻辑运算: ${true && true}

关系运算: ${a>b}

判空运算: ${empty obj} 主要用于判断字符串,集合是否为空,是空或为null及找不到值时都会输出true

获取11个内置(隐含)对象:${隐式对象名称}

隐含对象名称

描       述

例子

等价于

pageContext

对应于JSP页面中的pageContext对象(注意:取的是pageContext对象。)

${pageContext.request.contextPath}

<%=

pageContext.getRequest().getContextPath()

%>

或者:<%=request.getContextPath() %>

pageScope

代表page域中用于保存属性的Map对象

${pageScope.绑定名}

requestScope

代表request域中用于保存属性的Map对象

${requestScope.绑定名}

<%=request.getAttribute("绑定名") %>

sessionScope

代表session域中用于保存属性的Map对象

${sessionScope.绑定名}

<%=session.getAttribute("绑定名") %>

applicationScope

代表application域中用于保存属性的Map对象

${applicationScope.绑定名}

<%=application.getAttribute("绑定名") %>

param

表示一个保存了所有请求参数的Map对象

${param.name}或

${param['name']}

<%=

request.getParameter("name")

%>

paramValues

表示一个保存了所有请求参数的Map对象,它对于某个请求参数,返回的是一个string[]

${paramValues.name[0]}或

${paramValues['name'][0]}

<%=

request.getParameterValues("name")[0]

%>

header

表示一个保存了所有http请求头字段的Map对象

${header['host'] }<br/>

<%=request.getHeader("host") %>

headerValues

同上,返回string[]数组。注意:如果头里面有“-” ,例Accept-Encoding,则要headerValues [“Accept-Encoding”]

${headerValues['host'][0]}

<%=

request.getHeaders("host").nextElement() %>

cookie

表示一个保存了所有cookie的Map对象

${cookie['JSESSIONID'].name}

${cookie['JSESSIONID'].value}

<%=

request.getCookies()[index].getName()

%>

<%=

request.getCookies()[index].getValue()

%>

initParam

表示一个保存了所有web应用初始化参数的map对象

${initParam['AAA']}

<%=

application.getInitParameter("AAA") %>

注意

获取headerValues时,如果头里面有“-”,例Accept-Encoding,则要headerValues["Accept-Encoding"]

获取cookie时,例${cookie.key}取的是cookie对象,如访问cookie的名称和值,须${cookie.key.name}或${cookie.key.value}

有些Tomcat服务器如不能使用EL表达式:1)升级成tomcat6。 2)在JSP中加入<%@ page isELIgnored="false" %>

EL表达式保留关键字

And eq gt true

Or ne le false

No lt ge null

instanceof empty div mod

JSP内置标签:JSP Action(JSP动作)使用时不需要导入标签库

<jsp:include>:动态引入(先翻译再合并)(可携带参数)

<jsp:include page="relativeURL | <%=expression%>" flush="true|false" />

page属性用于指定被引入资源的相对路径,它也可以通过执行一个表达式来获得。

flush属性指定在插入其他资源的输出内容时,是否先将当前JSP页面的已输出的内容刷新到客户端,默认为false。

静态包含vs动态包含区别

原理不一样

语法不一样

能否传参

静态包含

先合并再翻译

include指令:<%@include%>

不能

动态包含

先翻译再合并

include标签:<jsp:include />

可以

<jsp:forward>:请求转发

<jsp:forward page="relativeURL | <%=expression%>" />

page属性用于指定请求转发到的资源的相对路径,它也可以通过执行一个表达式来获得。

<jsp:param>:为<jsp:include>和<jsp:forward>传参(放入彼标签内)

语法1:

<jsp:include page="relativeURL | <%=expression%>">

<jsp:param name="parameterName" value="parameterValue|<%= expression %>" />

</jsp:include>

语法2:

<jsp:forward page="relativeURL | <%=expression%>">

<jsp:param name="parameterName" value="parameterValue|<%= expression %>" />

</jsp:include>

<jsp:param>标签的name属性用于指定参数名,value属性用于指定参数值。在<jsp:include>和<jsp:forward>标签中可以使用多个<jsp:param>标签来传递多个参数。

JavaBean组件

<jsp:useBean>:用于在JSP页面中查找或实例化一个JavaBean组件。

<jsp:setProperty>:用于在JSP页面中设置一个JavaBean组件的属性。

<jsp:getProperty>:用于在JSP页面中获取一个JavaBean组件的属性。

标签

语法

<jsp:useBean>

在指定的域范围内查找指定名称的JavaBean对象。如果存在则直接返回该JavaBean对象的引用;如果不存在则实例化一个新的JavaBean对象并将它以指定的名称存储到指定的域范围中。

<jsp:useBean id="beanName" class="package.class" scope="page|request|session|application"/>

id属性:指定JavaBean实例对象的引用名称和其存储在域范围中的名称。

class属性:指定JavaBean的完整类名。

scope属性:指定JavaBean实例所存储的域范围:page(默认)、request、session和application。

带标签体的<jsp:useBean>标签

语法:<jsp:useBean ...>Body</jsp:useBean>

功能:Body部分的内容只在<jsp:useBean>标签创建JavaBean的实例对象时才执行。

注意:默认采用page域,每次都是没有的,所以执行,而如果是session域,第一次没有,以后都有,除非session过期,application类推…

<jsp:setProperty>

设置和访问JavaBean对象的属性。

<jsp:setProperty name="beanName" {property="propertyName" value="{string | <%= expression %>}" | property="propertyName" [ param="parameterName" ] | property= "*"

}/>

name属性:指定JavaBean对象的名称。

property属性:指定JavaBean实例对象的属性名。

value属性:指定JavaBean对象的某个属性的值,value的值可以是字符串,也可以是表达式。为字符串时,该值会自动转化为JavaBean属性相应的类型,如果value的值是一个表达式,那么该表达式的计算结果必须与所要设置的JavaBean属性的类型一致。

param属性:将JavaBean实例对象的某个属性值设置为一个请求参数值,该属性值同样会自动转换成要设置的JavaBean属性的类型。

注意:

1、在通过该标签设置Bean的属性值时对于八种基本类型,该标签可以将字符串自动转换,而其他类型无法转换。比如Date,param=“<%=new Date()%>”

2、param和*设置(param对请求参数的自动转型)

<jsp:getProperty>

读取JavaBean对象的属性。

<jsp:getProperty name="beanInstanceName" property="PropertyName" />

name属性:指定JavaBean实例对象的名称,其值应与<jsp:useBean>标签的id属性值相同。

property属性:指定JavaBean实例对象的属性名。

注意:

如果一个JavaBean实例对象的某个属性的值为null,那么,使用<jsp:getProperty>标签输出该属性的结果将是一个内容为“null”的字符串。

<%@ page language="java" import="java.util.*,entity.Student" pageEncoding="utf-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

  <head>

    <title>操作javabean标签</title>  

  </head>

  <body>

     <%--构造javabean对象:Student s = new Student(); --%>

     <jsp:useBean id="s" class="entity.Student"/>

     

     <%--给javabean对象赋值:s.setName("eric"); s.setAge(20);

     name: 给哪个对象赋值

     property: 给对象的哪个属性赋值

     value:赋的值

     --%>

     <jsp:setProperty name="s" property="name" value="eric"/>

     

     <%--获取javabean的属性:s.getName(); s.getAge();

     name:获取哪个对象

     property:获取对象的哪个属性

     --%>

     <jsp:getProperty name="s" property="name" />

  </body>

</html>

<jsp:useBean>原理

<jsp:useBean id="currentDate" class="java.util.Date"/>

翻译成的Servlet源码:

java.util.Date currentDate = null;

synchronized (_jspx_page_context) {

currentDate = (java.util.Date) _jspx_page_context.getAttribute(

"currentDate", PageContext.PAGE_SCOPE);

if (currentDate == null){

currentDate = new java.util.Date();

_jspx_page_context.setAttribute("currentDate",

currentDate, PageContext.PAGE_SCOPE);

}

}

JSTL标签标准标签

什么是JSTL

java standard tag libarary:Sun公司Java标准标签库,由apache组织负责维护。JSTL的发布包有两个版本:

Standard-1.0 Taglib(JSTL1.0):支持 Servlet2.3 和 JSP1.2 规范,Tomcat4 支持这些规范。

Standard-1.1 Taglib(JSTL1.1):支持 Servlet2.4 和 JSP2.0 规范,Tomcat5 支持这些规范。

jstl标签库分类

核心标签库(c)(重要)

国际化标签库(fmt)

JSTL函数库/EL函数库(fn)

SQL标签库(sql)

XML标签库(x)

如何使用JSTL

  1. 导入jstl的支持jar包到WEB-INF/lib目录下,以便于系统可以加载所需要的类。在java5.0之后jstl已经是属于5.0的支持包里面。
  2. 使用taglib指令导入需要的标签库以定位对应的类:uri命名空间与目标tld文件的<uri>一致,prefix前缀与目标tld文件的<short-name>一致。

语法:<%@taglib uri="目标tld文件中的<uri>" prefix="目标tld文件的<short-name>" %>

示例:<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

  1. 使用标签。

核心标签库的标签

保存域对象数据

<c:set var=”变量名” value=”变量值” scope=”保存到哪个域”/></c:set>

scope:page(默认)、request、session、application。

target:用于指定要设置属性的对象,这个对象必须是JavaBean对象或java.util.Map对象

property:用于指定当前要为对象设置的属性名称

获取域对象数据

<c:out value="变量值" default=”当value值为null或””时使用默认值代替” escapeXml=”是否转义输出(原样输出),默认为true”> </c:out>

例:<c:out value="${name}" default="<h3>标题3</h3>" escapeXml="false"></c:out>

value: EL表达式

单条件判断

<c:if test="条件表达式"></c:if>

例:<c:if test="${10>8}"><input type="submit" value="提交"/></c:if>

例:<c:if test="${user.gender =='m'}" var="rs" scope="request">男</c:if>

<c:if test="${!rs}">女</c:if>

注意:不要写成<c:if test="${user.gender }=='m'"></c:if>

正确写法应为:<c:if test="${user.gender =='m'}"></c:if>(运算符要写在EL表达式里面)

test为true则执行if标签体内容。

test true boolean 决定是否处理标签体中的内容的条件表达式

var:用于指定将test属性的执行结果保存到某个Web域中的某个属性的名称

scope:指定将test属性的执行结果保存到哪个Web域中

多条件判断

<c:choose>

<c:when test="条件表达式"></c:when>

<c:otherwise></c:otherwise>

</c:choose>

例:

<c:set var="user" value="eric" scope="session"></c:set>

<c:choose>

<c:when test="${!empty sessionScope.user}">

欢迎回来,你的用户名是 :${sessionScope.user},

<a href="">【退出登录】</a>

</c:when>

<c:otherwise>

请先<a href="">注册</a>或<a href="">登录</a>

</c:otherwise>

</c:choose>

test为true则执行if标签体内容。

遍历数组/List集合/Map集合

<c:forEach></c:forEach>

例:

 遍历List集合:

<c:forEach items="${list}" var="student" varStatus="varSta">

序号:${varSta.count }   姓名:${student.name1 } - 密码: ${student.password }<br/>

     </c:forEach>

遍历Map集合: forEach标签遍历Map集合时,会使用Entry封装键值对,通过getKey()获取键对象,通过getValue()获取值对象

<c:forEach items="${map}" var="entry">

编号: ${entry.key } - 姓名:${entry.value.name1 } - 密码: ${entry.value.password }<br/>

     </c:forEach>

begin:从哪个元素开始遍历(索引从0开始);

end:遍历到哪个元素;

如果指定items属性,就从集合中的第begin个元素开始进行迭代,begin的索引值从0开始编号;如果没有指定items属性,就从begin指定的值开始迭代,直到end值时结束迭代。

step:指定迭代步长,即迭代因子的迭代增量。默认1。

items: 需要遍历的数据(数组|List|Map|字符串)。如果是获取域数据,那么使用EL表达式获取;

var:指定将当前迭代到的元素保存到page域中的属性名称;

varStatus: 指定将代表当前迭代状态信息的对象保存到page域中的属性名称。例如 count属性: 表示当前遍历的是哪个元素,从1开始

varStatus: 指定将代表当前迭代状态信息的对象保存到page域中的属性名称。

拆分遍历字符串

<c:forToekens items=”需要遍历的字符串” delims=”指定分割符号,可多个” var=”每个内容的名称”></c:forTokens>

例:

<c:forTokens items="${str}" delims="-" var="s">${s },</c:forTokens>

URL重写

<c:url>

value:指定要构造的URL,/表示day15

var:指定将构造出的URL结果保存到Web域中的属性名称

scope:指定将构造出的URL结果保存到哪个Web域中

URL重写就是将会话标识号以参数形式附加在URL地址后面

重定向

<c:redirect url=””></c:redirect>

捕获异常

<c:catch [var="varName"]>nested actions</c:catch>

参数

在JSP页面进行URL的相关操作时,经常要在URL地址后面附加一些参数。<c:param>标签可以嵌套在<c:import>、<c:url>或<c:redirect>标签内,为这些标签所使用的URL地址附加参数。<c:param>标签在为一个URL地址附加参数时,将自动对参数值进行URL编码,例如,如果传递的参数值为“中国”,则将其转换为“%d6%d0%b9%fa”后再附加到URL地址后面,这也就是使用<c:param>标签的最大好处。

http://localhost:808/servlet/MyServlet?name=“中国”

示例:<c:param name=“name” value=“中国" />

自定义标签(简单标签)

当现有的标签无法满足需求的时候,就需要开发者自行开发标签。

开发步骤

1)开发标签处理程序:编写一个继承SimpleTagSupport的java类,覆盖doTag方法

package tag;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.JspWriter;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.SimpleTagSupport;

public class HelloTag extends SimpleTagSupport {

private String info;

private int qty;

// 对应标签属性,在jsp页面赋值

public void setInfo(String info) {

this.info = info;

}

// 对应标签属性,在jsp页面赋值

public void setQty(int qty) {

this.qty = qty;

}

@Override

public void doTag() throws JspException, IOException {

PageContext pageContext = (PageContext) this.getJspContext();

JspWriter out = pageContext.getOut();

// 通过PageContext获取到其他8个内置对象

HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();

// 获取客户端的IP地址

String ip = request.getRemoteHost();

// 输出IP地址

out.write("当前客户端的IP为:" + ip + "<br />");

// 输出在jsp中定义的内容

for (int i = 0; i < qty; i++) {

out.println(info + "<br/>");

}

}

}

2)在项目的WEB-INF目录下建立tld文件,用于声明标签库信息和标签信息,内容可参考核心标签库的c.tld内容

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"

version="2.0">

<!-- 标签库的版本号 -->

<tlib-version>1.1</tlib-version>

<!-- 标签库的简单名称对应于taglib指令的prefix属性值 -->

<short-name>c1</short-name>

<!-- 标签库的唯一名称 ,对应于taglib指令的uri属性值-->

<uri>http://www.tarena.com.cn/mytag</uri>

<!-- 定义一个标签 -->

<tag>

<!-- 标签名称 -->

<name>hello</name>

<!-- 标签处理程序的全名: 包名+类名 -->

<tag-class>tag.HelloTag</tag-class>

<!--

标签体输出格式

JSP: 可以写jsp脚本或表达式,也可以执行!(只能用在传统标签)

scriptless: 不能写jsp脚本或表达式!(可以是EL和JSP动作)

empty: 没有标签体

tagdependent: 可以写jsp脚本或表达式,但按照脚本的原样输出(EL也是原样输出)

-->

<!-- 标签体内容输出格式 -->

<body-content>empty</body-content>

<!-- 定义标签属性 -->

<attribute>

<name>info</name>

<required>true</required>

<rtexprvalue>false</rtexprvalue>

</attribute>

<!-- 定义标签属性 -->

<attribute>

<name>qty</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

</taglib>

3)在jsp页面顶部导入自定义标签库并使用库中标签

<%@ page language="java" contentType="text/html;" pageEncoding="utf-8"%>

<%-- 导入自定义标签库 --%>

<%@taglib uri="http://www.tarena.com.cn/mytag" prefix="c1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>Insert title here</title>

</head>

<body>

<%-- 使用标签库中的标签 --%>

<c1:hello info="hello kitty" qty="10" />

</body>

</html>

执行过程

前提: tomcat启动的时候,会读取day12项目下的WEB-INF的文件。包括web.xml和tld文件。

1)翻译成java文件-》 编译成class文件-> 构造方法- 》 调用_jspService()方法

2)依据原jsp中uri属性(对应<uri>)找到tld文件

3)依据原jsp中<c1:hello>中的hello(对应<name>)找到tag标签

4)依据tld文件中<tag-class>找到标签类(tag.HelloTag

5)实例化该标签,同时属性值赋给参数,调用doTag方法。

生命周期

实现了SimpleTag接口的类就是标签处理程序类,写标签处理程序类一般继承SimpleTag接口的实现类SimpleTagSupport即可!

标签处理程序类由Tomcat服务器调用。

1)构造方法 构造标签器对象

2)void setJspContext(JspContext context) 传入PageContext对象。JspContext是PageContext的父类。可用getJspContext()获取PageContext对象。

3)void setParent(JspTag parent) 传入父标签对象。如果没有父标签,则不调用此方法。使用getParent()获取父标签。

4)setXxx() 设置标签属性。只有定义了属性才调用该方法。

5)void setJspBody(JspFragment jspBody) 传入标签体内容。如果没有标签体,则不调用此方法。使用getJspBody()获取标签体内容。

6)void doTag() 调用标签时执行方法。业务逻辑就写在这个方法中。可调用getJspContext()、getParent()、getJspBody()。

//  itcast:showIp

    gz.itcast.a_tag.ShowIpTag_jspx_th_itcast_005fshowIp_005f0 = new gz.itcast.a_tag.ShowIpTag();

    org.apache.jasper.runtime.AnnotationHelper.postConstruct(_jsp_annotationprocessor, _jspx_th_itcast_005fshowIp_005f0);

    _jspx_th_itcast_005fshowIp_005f0.setJspContext(_jspx_page_context);

    _jspx_th_itcast_005fshowIp_005f0.setParent(_jspx_parent);

    _jspx_th_itcast_005fshowIp_005f0.setJspBody(new Helper( 1, _jspx_page_context, _jspx_th_itcast_005fshowIp_005f0, null));

    _jspx_th_itcast_005fshowIp_005f0.doTag();

自定义标签使用

1)控制标签体内容是否输出

输出:调用getJspBody().invoke(null) 默认null,把标签体内容输出到浏览器。此处null等价于getJspContext().getOut()

不输出:不调用

2)控制标签后面的内容是否输出

执行:什么都不做

不执行:抛出SkipPageException异常:throw new SkipPageException();

3)重复输出标签体内容

循环执行jspFragment.invoke(null)

4)修改标签体内容

// 创建临时容器

StringWriter sw = new StringWriter();

// 把标签体内容拷贝到临时容器

jspBody.invoke(sw);

// 从Writr流中获取标签体内容

String content = sw.toString();

// 修改标签体内容

content = content.toLowerCase();

// 手动输出到浏览器

getJspContext().getOut().write(content);

5)带属性的标签

a)在标签处理器类中,添加属性成员变量和一个对应的set方法,用于接收标签的属性值

public class DemoTag1 extends SimpleTagSupport{

//声明一个成员变量用于接收标签属性值

private Integer num;

//用于传入标签属性值

public void setNum(Integer num) {

this.num = num;

}

b)在tld文件的tag标签中,添加一个属性的声明

<tag>

<name>demo1</name>

<tag-class>gz.itcast.a_tag.DemoTag1</tag-class>

<body-content>scriptless</body-content>

<!-- 声明属性 -->

<attribute>

<!-- description用于指定属性的描述信息;type用于指定属性值的Java类型 -->

<!-- 属性名称,大小写敏感,并且不能以jsp、_jsp、java和sun开头 -->

<name>num</name>

<!—指定该属性是否必须。默认false -->

<required>true</required>

<!-- runtime expression value(运行时表达式),指定该属性是否支持JSP或EL表达式。默认false不支持 -->

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

c)在jsp页面中使用属性

<itcast:demo1 num="20">AAAAAA<br/></itcast:demo1>

高仿核心标签库forEach

ForEachTag.java

foreach.jsp

package gz.itcast.a_tag;

import java.io.IOException;

import java.util.Collection;

import java.util.List;

import java.util.Map;

import javax.servlet.jsp.JspContext;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.SimpleTagSupport;

public class ForEachTag extends SimpleTagSupport {

private Object items;//需要遍历的对象(List或Map)

private String var;//每个元素的名

public void setItems(Object items) {

this.items = items;

}

public void setVar(String var) {

this.var = var;

}

@Override

public void doTag() throws JspException, IOException {

//得到pageContext对象

JspContext jspContext = this.getJspContext();

PageContext pageContext = (PageContext)jspContext;

/*//如果List,强转成List

if(items instanceof List){

List list = (List)items;

//遍历List

for(Object obj:list){

//把obj放入域对象中

pageContext.setAttribute(var, obj);

//把标签体内容输出到浏览器

this.getJspBody().invoke(null);

}

}

//如果是Map,强转成Map

if(items instanceof Map){

Map map = (Map)items;

//使用entrySet方式遍历

Set entrySet = map.entrySet();

for(Object obj:entrySet){

//把obj放入域对象中

pageContext.setAttribute(var, obj);

//把标签体内容输出到浏览器

this.getJspBody().invoke(null);

}

}*/

//List和Map的数据转到Collection

Collection colls = null;

if(items instanceof List){

colls = (List)items;

}

if(items instanceof Map){

Map map = (Map)items;

colls = map.entrySet();

}

//遍历

for (Object obj : colls) {

//把obj放入域对象中

pageContext.setAttribute(var, obj);

//把标签体内容输出到浏览器

this.getJspBody().invoke(null);

}

}

}

<%@ page language="java" import="java.util.*,gz.itcast.a_tag.*" pageEncoding="utf-8"%>

<%@ taglib uri="http://gz.itcast.cn" prefix="itcast"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

  <head> 

    <title>forEach标签</title>  

  </head>

  

  <body>

    <%

    List<Student> list = new ArrayList<Student>();

    list.add(new Student("eric",20));

    list.add(new Student("jacky",30));

    list.add(new Student("rose",40));

    pageContext.setAttribute("list",list);

   

    Map<String,Student> map = new HashMap<String,Student>();

    map.put("001",new Student("lily",20));

    map.put("002",new Student("mark",30));

    map.put("003",new Student("lucy",40));

    pageContext.setAttribute("map",map);

     %>

     

     <itcast:forEach items="${list}" var="student">

      姓名: ${student.name } - 年龄:${student.age }<br/>

     </itcast:forEach>

     <hr/>

     <itcast:forEach items="${map}" var="entry">

      编号: ${entry.key } - 姓名:${entry.value.name } - 年龄: ${entry.value.age }<br/>

     </itcast:forEach>

     

  </body>

</html>

itcast.tld

<!-- forEach标签 -->

<tag>

<name>forEach</name>

<tag-class>gz.itcast.a_tag.ForEachTag</tag-class>

<body-content>scriptless</body-content>

<attribute>

<name>var</name>

<required>true</required>

<rtexprvalue>false</rtexprvalue>

</attribute>

<attribute>

<name>items</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

</tag>

什么是过滤器

过滤器就是一个实现了Filter接口(javax.servlet.Filter)的对象,该对象可以拦截请求或响应并执行过滤任务。

过滤器是servlet的三大组件(Servlet、过滤器、监听器)之一。servlet组件的特点:把组件配置到web.xml文件中,组件就可以交给tomcat服务器运行!

过滤器的生命周期

生命周期

何时调用

调用次数

参数

构造方法

Web容器启动时即调用以创建过滤器对象

1次。单实例多线程。

init方法

创建完过滤器对象之后调用

1次

FilterConfig:过滤器配置对象,用于获取在web.xml中配置的过滤器初始化参数(类似于ServletConfig之于Servlet)

doFilter方法

每次访问指定目标资源的时候调用

n次

ServletRequest:是HttpServletRequest的父类,可强转成后者。

ServletResponse:是HttpServletResponse的父类,可强转成后者。

FilterChain:过滤器链,负责多过滤器的传递。

destroy方法

项目重新部署或服务器停止时调用以销毁过滤器对象。

1次

过滤器的初始化参数:FilterConfig对象

FilterConfig对象,过滤器配置对象,用于加载过滤器的参数配置,以及获取ServletContext对象(filterConfig.getServletContext())

过滤器初始化参数使用:

1)在web.xml文件中配置初始化参数

2)在过滤器类中通过FilterConfig对象读取初始化参数

<filter>

<filter-name>HelloFilter</filter-name>

<filter-class>hello.HelloFilter</filter-class>

<init-param>

<param-name>AAA</param-name>

<param-value>AAA'value</param-value>

</init-param>

<init-param>

<param-name>BBB</param-name>

<param-value>BBB'value</param-value>

</init-param>

</filter>

public class CommentFilter implements Filter{

private FilterConfig filterConfig;    

public void init(FilterConfig arg0) throws ServletException {

filterConfig = arg0;

}

public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {

//得到一个参数

System.out.println(filterConfig.getInitParameter("AAA"));

//遍历所有参数

Enumeration<String> enums = filterConfig.getInitParameterNames();

while(enums.hasMoreElements()){

String paramName = enums.nextElement();

String paramValue = filterConfig.getInitParameter(paramName);

System.out.println(paramName+"="+paramValue);

}

}

public void destroy() {

}

}

过滤器的优先级:过滤器链

doFilter(ServletRequest request, ServletResponse response, FilterChain chain):

参数一: ServletRequest是HttpServletRequest的父接口。实际上传入的是HttpServletRequest接口的实现类。

参数二: ServletResponse是HttpServletResponse的父接口。实际上传入HttpServletResponse接口的实现类。

参数三: FilterChain过滤器链接口。filterChain也提供了一个doFilter方法:

void doFilter(ServletRequest request, ServletResponse response)

可根据需求决定是否调用此方法。调用该方法,则web服务器就会执行过滤器链中的下一个过滤器,若无则执行目标资源。

过滤器链中的过滤器优先级:由web.xml中filter-mapping的排列顺序决定。

装饰者模式(Decorator Pattern)

当某些类的某些方法不满足需要,可编写装饰类去继承这些类(被装饰类),重写这些类的方法以添加新功能。

1)编写一个装饰类继承被装饰类,被装饰类不能是final的。

2)在装饰类中定义一个成员变量(被装饰类类型),用于接收被装饰类对象。

3)在装饰类的构造方法中传入被装饰类对象,赋值给成员变量。

4)在装饰类类中重写被装饰类方法,添加新功能。

Servlet API中提供了request和response的Decorator设计模式的默认实现类HttpServletRequestWrapper和HttpServletResponseWrapper, (HttpServletRequestWrapper类和HttpServletResponseWrapper类实现了request和response接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的request和和response对象的对应方法)以避免用户在对request和和response对象进行增强时需要实现request和和response接口中的所有方法。

package gz.itcast.c_decorator;

import java.io.BufferedReader;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

import java.io.Reader;

/**

 * 装饰者模式案例:使用Decorator设计模式为BufferedReader类的readLine方法添加行号的功能

 */

public class Demo1 {

public static void main(String[] args) throws Exception {

//oldMethod();

BufferedReader b = new BufferedReader(new FileReader("e:/names.txt"));

//对BufferedReader进行装饰

BufferedReader br = new MyBufferedReader(b);

String str = null;

while( (str = br.readLine())!=null){

System.out.println(str);

}

}

private static void oldMethod() throws FileNotFoundException, IOException {

BufferedReader br = new BufferedReader(new FileReader("e:/names.txt"));

String str = null;

while( (str = br.readLine())!=null){

System.out.println(str);

}

}

}

/**

 * 1)编写一个装饰类MyBufferedReader继承非final的被装饰类BufferedReader。

*/

class MyBufferedReader extends BufferedReader{

/**

 * 2)在装饰类中定义一个成员变量(被装饰类类型),用于接收被装饰类对象。

 */

private BufferedReader br;

/**

 * 3)在装饰类的构造方法中传入被装饰类对象,赋值给成员变量。

 */

public MyBufferedReader(Reader in) {

super(in);

this.br = (BufferedReader)in;

}

int count = 1;

/**

 * 4)在在装饰类类中重写被装饰类方法,添加新功能。

 */

@Override

public String readLine() throws IOException {

//获取原来的内容

String content = br.readLine();

//添加新功能:加上序号

if(content!=null){

content = count+":"+content;

count++;

}

//返回修改后的内容

return content;

}

}

过滤器的执行流程(单个、多个)

如何编写过滤器

步骤一、编写一个实现Filter接口的类,实现Filter接口的三个方法,过滤逻辑在doFilter方法中实现。

步骤二、在web.xml文件中注册过滤器。

注册过滤器

<!-- 过滤器配置 -->

<filter>

<filter-name>filter1</filter-name><!-- 过滤器名称自定义一般写类名 -->

<filter-class>web.CommentFilter</filter-class><!-- 过滤器类的全名包名+类名 -->

<!-- 初始化参数FilterConfig对象读取 -->

<init-param>

<param-name>illegalStr</param-name>

<param-value>去年买了个表</param-value>

</init-param>

</filter>

<!-- 过滤器映射配置 -->

<filter-mapping>

<filter-name>filter1</filter-name><!-- 过滤器名称与上面的过滤器名称保持一致 -->

<url-pattern>/comment</url-pattern><!—要过滤的资源的路径,不是过滤器的路径! -->

</filter-mapping>

1)过滤路径映射规则同Servlet路径映射规则。

2)过滤路径要么以斜杠开头,要么以*开头。

3)不能同时使用两个模糊过滤,例如 /*.do是非法的

4)如果存在多个需要被过滤的资源,可以写多个url-pattern去过滤。单个资源也可使用多个过滤器。

5)如果过滤动态资源Servlet,可以使用Servlet的访问名称,也可以使用Servlet名称:

   <filter-mapping>

<filter-name>filter1</filter-name>

<servlet-name>HelloServlet</servlet-name>

</filter-mapping>

6)过滤类型:

   <filter-mapping>

<filter-name>filter1</filter-name>

<servlet-name>HelloServlet</servlet-name>

<!-- 过滤类型:声明哪种访问才可以被拦截(过滤),可以设置多个 -->

<dispatcher>REQUEST</dispatcher><!-- 默认:来自于请求的访问才可以被拦截 -->

<dispatcher>FORWARD</dispatcher><!-- 来自于转发的访问才可以被拦截 -->

<dispatcher>INCLUDE</dispatcher><!-- 来自于包含的访问才可以被拦截 -->

<dispatcher>ERROR</dispatcher><!-- 来自于错误的访问才可以被拦截 -->

</filter-mapping>

过滤器常见应用

设置请求编码(使用Decorator模式装饰request对象,完全解决get、post请求方式下的乱码问题。)

package gz.itcast.d_cases;

import java.io.IOException;

import java.io.UnsupportedEncodingException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

/**

 * 请求参数中文乱码过滤器

 */

public class EncodingFilter implements Filter {

public void destroy() {

}

public void init(FilterConfig filterConfig) throws ServletException {

}

/**

 * 执行过滤任务

 */

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {

//强制转换请求

HttpServletRequest request = (HttpServletRequest)req;

/**

 * 0)解决post提交参数乱码问题

 */

request.setCharacterEncoding("utf-8");

/**

 * 5)解决get提交参数乱码问题:

 */

//创建一个HttpServletRequest的实现类的装饰类(重写了getParameter方法)的实例,在放行时替换原request对象。

MyHttpRequest myRequest = new MyHttpRequest(request);

/**

 * 放行。这里放行的应该是装饰后的request对象,这样在目标Servlet的service方法中调用的getParameter方法才是重写后的方法。

 */

chain.doFilter(myRequest, resp);

}

}

/**

 * 1)编写一个装饰类继承HttpServletRequest的非final实现类HttpServletRequestWrapper。

 */

class MyHttpRequest extends HttpServletRequestWrapper{

/**

 * 2)声明一个被装饰者类型的成员变量

 */

private HttpServletRequest request;

/**

 * 3)在构造方法中传入被装饰类对象,赋值给成员变量。

 */

public MyHttpRequest(HttpServletRequest request) {

super(request);

this.request = request;

}

/**

 * 4)重写getParameter方法,添加新功能:处理get提交参数的编码

 */

@Override

public String getParameter(String name) {

try {

//得到原来的参数,原编码是iso-8859-1

String value = request.getParameter(name);

//手动解码

if("GET".equals(request.getMethod())){

value = new String(value.getBytes("iso-8859-1"),"utf-8");

}

return value;

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

throw new RuntimeException(e);

}

}

另外两个方法getParameterValues和getParameterMap也要重写

}

web.xml关键代码:

<filter>

<filter-name>EncodingFilter</filter-name>

<filter-class>gz.itcast.d_cases.EncodingFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>EncodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

压缩网页内容(使用Decorator模式装饰response对象获取响应实体内容,使用java的GZIPOutputStream类对实体内容进行gzip压缩)

思路:

通过filter向目标页面传递一个自定义的response对象。

在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。

在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中。

当页面完成输出后,在filter中就可得到页面写出的数据,从而我们可以调用GzipOuputStream对数据进行压缩后再写出给浏览器,以此完成响应正文件压缩功能。

package gz.itcast.d_cases;

import java.io.ByteArrayOutputStream;

import java.io.CharArrayWriter;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpServletResponseWrapper;

/**

 * 网页压缩过滤器

 */

public class GZIPFilter implements Filter {

public void destroy() {

}

public void init(FilterConfig filterConfig) throws ServletException {

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

//创建一个response的装饰者对象

MyHttpResponse myResponse = new MyHttpResponse((HttpServletResponse)response);

//放行myResponse取代response

chain.doFilter(request, myResponse);

/**

 * 过滤响应之获取myResponse的实体内容

 */

//

//从缓存容器对象得到压缩前的内容

//注意:response对象中没有方法获取实体内容,怎么办?

char[] content = myResponse.getCharArray();

/**

 * 过滤响应之用gzip压缩实体内容

 */

//创建临时的字节数组容器(节点流)

ByteArrayOutputStream buf = new ByteArrayOutputStream();

//创建套在buf之上的GZIPOutputStream对象(处理流)

GZIPOutputStream gzip = new GZIPOutputStream(buf);

//开始压缩内容(通过处理流压缩内容并写入缓冲区)

gzip.write(new String(content).getBytes()); // gzip.write(content.toString().getBytes())?

//刷新缓冲区将压缩内容写入节点流

gzip.finish();

//从临时的字节数组容器中得到压缩后的网页内容

byte[] result = buf.toByteArray();

/**

 * 过滤响应之发送压缩后的响应实体内容

 */

//发送响应头告诉浏览器数据压缩格式

myResponse.setHeader("content-encoding", "gzip");

//把压缩后的内容输出到浏览器(用getOutputStream(),不要用getWriter(),否则导致循环调用getWriter()

response.getOutputStream().write(result);

// 不要用:myRresponse.getWriter().write(new String(result,0,result.length));

}

}

/**

 * HttpServletResponse的装饰者类

 */

class MyHttpResponse extends HttpServletResponseWrapper{

/**

 * 声明一个被装饰者类型的成员变量

 */

private HttpServletResponse response;

/**

 * 定义一个缓存容器对象

 */

private CharArrayWriter charArray = new CharArrayWriter();

/**

 * 提供一个获取charArray中压缩后的网页内容的方法

 */

public char[] getCharArray(){

return charArray.toCharArray();

}

/**

 * 在构造方法中传入被装饰类对象,赋值给成员变量。

 */

public MyHttpResponse(HttpServletResponse response) {

super(response);

this.response = response;

}

/**

 * 重写getWriter()方法,让其返回一个带CharArrayWriter缓存容器的PrintWriter

 */

@Override

public PrintWriter getWriter() throws IOException {

//如果我们调用带缓存PrintWriter对象的write()方法,那么内容会直接写入到CharrArrayWriter缓存容器中。

return new PrintWriter(charArray);

}

}

package gz.itcast.d_cases;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class ContentServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

//准备内容

StringBuffer sb = new StringBuffer();

for(int i=1;i<=3000;i++){

sb.append("abcd");

}

//写出数据。此处response是装饰后的response,write方法将输出实体内容到CharArrayWriter中而非浏览器,输出到浏览器改由网页压缩过滤器执行。

response.getWriter().write(sb.toString());

}

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

doGet(request, response);

}

}

web.xml关键代码:

<!-- 网页内容压缩过滤器 -->

<filter>

<filter-name>GZIPFilter</filter-name>

<filter-class>gz.itcast.d_cases.GZIPFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>GZIPFilter</filter-name>

<url-pattern>/content</url-pattern>

</filter-mapping>

<servlet>

<servlet-name>ContentServlet</servlet-name>

<servlet-class>gz.itcast.d_cases.ContentServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ContentServlet</servlet-name>

<url-pattern>/content</url-pattern>

</servlet-mapping>

设置登录权限注意不能拦截LoginServet和login.jsp等登录请求!

package gz.itcast.web;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

/**

 * 登录权限过滤器

 */

public class SecurityFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {

//强制转换请求和响应

HttpServletRequest request = (HttpServletRequest)req;

HttpServletResponse response = (HttpServletResponse)resp;

//是否登录判断逻辑

HttpSession session = request.getSession(false);

if(session==null || (String)session.getAttribute("user")==null){

//未登录或session过期,则重定向至无授权提示页面!

response.sendRedirect(request.getContextPath()+"/noAuth.html");

}else{

//已登录,则放行!

chain.doFilter(request, response);

}

}

public void destroy() {

}

public void init(FilterConfig filterConfig) throws ServletException {

}

}

web.xml关键代码:

<filter>

<filter-name>SecurityFilter</filter-name>

<filter-class>gz.itcast.web.SecurityFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>SecurityFilter</filter-name>

<!—注意不能拦截LoginServet和login.jsp等登录请求! -->

<url-pattern>/user/*</url-pattern>

</filter-mapping>

过滤用户权限

过滤敏感字符

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.*;

import javax.servlet.http.*;

public class CommentFilter implements Filter{

        private FilterConfig config;

    

        public void destroy() {

            System.out.println("CommentFilter1's destroy...");

        }

        public void doFilter(ServletRequest arg0,

                              ServletResponse arg1, FilterChain arg2)

                              throws IOException, ServletException {

                HttpServletRequest request = (HttpServletRequest)arg0;

                HttpServletResponse response =(HttpServletResponse)arg1;

                request.setCharacterEncoding("utf-8");

                response.setContentType("text/html;charset=utf-8");

                PrintWriter out = response.getWriter();

                String content = request.getParameter("content");

                String illegalStr = config.getInitParameter("illegalStr");

                if(content.indexOf(illegalStr) != -1){

                    //有敏感字

                    out.println("<h1>评论内容包含了敏感字</h1>");

                }else{

                    //没有敏感字

                    // 执行FilterChain的doFilter会调用后续的过滤器或者servlet。

                    arg2.doFilter(arg0, arg1);

                }

                System.out.println("Filter1's doFilter end.");

        }

    

        public void init(FilterConfig arg0) throws ServletException {

            System.out.println("CommentFilter1's init...");

            config = arg0;

        }

}

web.xml关键代码:

<filter>

<filter-name>filter1</filter-name>

<filter-class>web.CommentFilter</filter-class>

<init-param>

<param-name>illegalStr</param-name>

<param-value>去年买了个表</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>filter1</filter-name>

<url-pattern>/comment</url-pattern>

</filter-mapping>

什么是监听器

servlet规范当中定义的一种特殊的组件,用来监听servlet容器产生的事件并进行响应的处理。

生命周期相关的事件

容器创建或者销毁request,session,ServletContext(上下文/环境)时产生的事件(统计在线人数)。

绑定数据相关的事件

调用了以上三个对象(request,response,ServletContext)的setAttribute,removeAttribute方法时产生的事件。

事件源

监听器

触发时机

如何编写监听器

步骤一、编写Java类实现相应的监听器接口

共有8个监听器接口,要依据监听的事件类型来选择相应的监听器接口,比如要监听session对象的创建和销毁,就实现HttpSessionListener。

public class CountListener implements HttpSessionListener{

private int count = 0;

public void sessionCreated(HttpSessionEvent arg0){

System.out.println("sessionCreated…");

count ++;

}

public sessionDestroyed(HttpSessionEvent arg0){

System.out.println("session destroyed…");

count--;

}

}

步骤二、在监听器接口方法中实现相应的监听处理逻辑

比如,session对象被删除了,将人数减1。

public void sessionCreated(HttpSessionEvent arg0){

System.out.print("sessionCreated…");

HttpSession session = args.getSession();

ServletContext ctx = session.getServletContext();

ctx.setAttribute("count",count);

}

步骤三、在web.xml文件注册监听器

<listener>

    <listener-class>web.CountListener</listener-class>

</listener>

监听器的应用场景

系统框架级别的代码经常需要检测容器中数据或对象的变化,以一个不受人为控制因素的触发为执行时机,所以对于需要根据数据变化来做出自动反馈的功能都可以使用到监听器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值