1、JDBC的诞生
1.1 JDBC的诞生
JDBC是 Java 连接数据库的标准,是连接数据库的抽象层,由Java 编写的一组类和接口组成,接口的实现由各个数据库厂商来完成。
这个标准就是指Connection、Statement、ResultSet接口,分别用于连接数据库,执行SQL语句、返回结果。从Connetion 可以创建Statement,Statement执行查询后可以得到ResultSet。这些接口的具体实现都是由各个数据库厂商来完成。
如果直接new 一个 Connection 实现类对象的话,当Connection实现类版本更迭,名称修改了就无法使用了。因此我们提倡面向接口编程而不是向实现编程。由电脑硬件驱动启发,创建一个抽象层 Driver,通过Driver来获取数据库连接Connection。但是Drive的实现类也不能直接new,所以通过反射,在运行时动态装载。例如mysql 的Driver是这样的:Class.forName(“com.mysql.jdbc.Driver”);Oracle 的是这样的:Class.forName(“oracle.jdbc.driver.OracleDriver”);只要Driver不变,其具体的实现像Connection, Statement, ResultSet想怎么改就怎么改。
还有一个问题就是 一个程序可能访问不同的数据库,可能有多个不同的Driver都被动态加载,如何管理?
通过DriverManager类,将mysql的Driver、Oracle的Driver在类的初始化时,注册到DriverManager中,这样就可以通过DriverManager来管理。
注意: 关键点是static 的代码块, 在一个类被装载后就会执行。
//简化版DriverManager
public class DriverManager{
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
for(DriverInfo aDriver : registeredDrivers) {
Connection conn = aDriver.getConnection(url,info);
if(conn!=null){
return conn;
}
}
throw new RuntimeException("cant not create a connection")
}
//把实现类的Driver注册进一个ArrayList里
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
}
}
DriverManager会提供一个getConnection的方法,用于建立数据库Connection,只需要提供参数url、user、password。DriverManager会把这些信息传递给每个Driver , 让每个Driver去创建Connection
1.2 连接数据库的步骤
- 加载注册驱动
- 获取连接对象
- 创建语句对象
- 执行Sql语句
- 释放资源
1.3 数据库连接池
- 什么是数据库连接池?
是程序启动时创建足够多个数据库连接,将这些连接组成一个连接池,由程序动态的对池中的连接进行申请、使用、释放。
连接是一种状态,即彼此感受到对方的存在,建立连接是维护一种状态,维护状态就需要一定的数据结构,数据库连接池就可以将建立的连接用链表存储起来。
- 什么要使用数据库连接池?
- 数据库的每一次连接都要经历三次握手,mysql认证,执行sql,四次挥手,这是十分耗费资源和时间的;
- 并且每一次的连接都没有很好的重复利用,如果并发量大的话,将会还会占用更多的系统资源;
- 我们何不在程序初始化时,集中创建多个数据库连接,集中分配、管理、关闭,这样即可加快速度,充分利用资源。
- 连接池的分类
在Java中,连接池使用javax.sql.Datasource接口来表示连接池,Java中没有提供连接池的实现。
注意:DataSource仅仅只是一个接口,由各大厂商来实现。
常用的DataSource的实现:
- DBCP:Spring推荐的、
- C3PO: Hibernate推荐的
- Druid:阿里巴巴提供,号称世界上性能最好的连接池
- Datasource(数据源)和连接池(connection Pool)是同一个
2、Tomcat(web服务器)
2.1 介绍
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说它是最佳的选择。Tomcat 实际上运行JSP 页面和Servlet。
2.2 Tomcat目录结构介绍
- bin:主要存放tomcat命令,分为两大类
- 一类是以.sh结尾的(Linux命令)
- 另一类是以.bat结尾的(Windows命令)
- startup 用来启动tomcat
- shutdown 用来关闭tomcat
- conf:主要存放tomcat配置文件
- server.xml 可以设置端口号、设置域名或IP、默认加载的项目、请求编码.
- web.xml
- lib:主要存放tomcat运行依赖的jar包
- logs:存放tomcat运行过程产生的日志文件(清空不会产生影响)
- 在windows环境中,控制台的输出日志在catalina.xxxx-xx-xx.log文件中
- 在linux环境中,控制台的输出日志在catalina.out文件中
- temp:存放tomcat运行过程中产生的临时文件
- webapps:用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用
- work:用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件。清空work目录,然后重启tomcat,可以达到清除缓存的作用
3、servlet
3.1 什么是Servlet?
- Servlet是基于Java技术的web组件,是一个接口、一个类;运行在服务器端,由Servlet容器所管理。
- Servlet还是一个动态网页规范;编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。
- Servlet 功能就是用于浏览器和服务端进行动态交互,生成动态网页。
管理运行 Servet/JSP 的容器称为Web容器。Servlet 容器=JSP容器=Web容器 ,Servlet不能独立运行,必须部署到Servlet容器中,由容器实例化和调用 Servlet方法,生命周期由容器所管理。
请求处理过程:
- 用户通过URL请求访问Servlet,Web服务器接收到请求,并不是直接交给Servlet,而是交给Servet容器。
- Servlet容器实例化Servlet,调用Servlet的特定方法对请求进行处理,并产生一个响应。
- 这个响应由Web 服务器包装并返回给Web服务器,以HTTP响应的形式发给Web浏览器。
注意:Servlet必须有公共的无参数构造器
原因:底层使用的是反射的方式创建对象,用到了Class对象.newInstance()方法;该方法要求类有公共无参数构造器
3.2 Servlet API 组成
Java Servlet API由两个软件包组成:一个是对应HTTP的软件包,另一个是不对应HTTP的通用的软件包
-
软件包:
javax.servlet- 所包含的接口:
Servlet
ServletRequest
ServletResponse
RequestDispatcher
ServletConfig
ServletContext
SingleThreadModel
Filter
FilterConfig
FilterChain
ServletRequestListener
ServletRequestAttributeListener
ServletContextListener
ServletContextAttributeListener
- 所包含的类:
GenericServlet
ServletInputStream
ServletOutputStream
ServletRequestWrapper
ServletResponseWrapper
ServletRequestEvent
ServletContextEvent
ServletRequestAttributeEvent
ServletContextAttributeEvent
ServletException
UnavailableException
- 所包含的接口:
-
软件包:
javax.servlet.http- 所包含的接口:
HttpServletRequest
HttpServletResponse
HttpSession
HttpSessionListener
HttpSessionAttributeListener
HttpSessionBindingListener
HttpSessionActivationListener
- 所包含的类:
HttpServlet
Cookie
HttpServletRequestWrapper
HttpServletResponseWrapper
HttpSessionEvent
HttpSessionBindingEvent
- 所包含的接口:
Servlet API主要的类和接口:
1、Servlet 接口
1、init
void init(ServletConfig config) ;
-
对于每个 Servlet 实例,
init()方法只能被调用一次。 -
在 Servlet 实例化后,Servlet 容器会调用
init() 方法来初始化该对象。 -
init() 方法上ServletConfig类型的参数,可以用来获取web.xml中的配置的初始化参数信息。 -
另外,还可以通过
ServletConfig对象获取描述Servlet 运行环境的ServletContext对象,使用该对象,Servlet 可以和它的Servlet容器进行通信。
2、service
void service(ServletRequest req, ServletResponse res) ;
- 对于每个请求,
service()方法都会执行一次。 - 容器调用
service() 方法来处理客户端请求,在调用该方式时,要确保init()方法完成。 - 容器会构造一个表示客户端请求信息的请求对象(类型为
ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。
3、destory
void destroy() ;
由servlet容器调用,表示servlet正在退出服务。在Servlet 容器调用 destory()方法前,如果还有其他的线程正在 service()方法中执行,容器会等等这些线程执行完毕或等待服务器设定的超时值到达。
4、getServletConfig
ServletConfig getServletConfig() ;
- 该方法返回容器调用
init()方式时传递给Servlet 对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。 - 作为一个Servlet的开发者,你应该通过
init()方法存储ServletConfig对象以便这个方法能返回这个对象。为了你的便利,GenericServlet在执行这个接口时,已经这样做了。
5、getServletInfo
String getServletInfo() ;
- 返回Servlet类的信息,作者,版权等信息。几乎不用。
2、ServletConfig 接口
在javax.servlet 包中,定义了ServletConfig接口。Servlet容器使用ServletConfig对象在Servlet初始化期间向它传递配置信息。一个Servlet只有一个ServletConfig对象。
1、getInitParameter
public String getInitParameter(String name);
-
用于获取Servlet配置的初始化参数,该参数是程序员在开发的时候配置在
web.xml中的-
<!--web.xml--> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--告诉spring去哪里寻找配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext_website.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
-
2、getInitParameterNames
public Enumeration getInitParameterNames();
- 返回所有初始化参数的名字的集合。如果Servlet没有初始化参数,返回一个空的集合。
3、getServletContext
public ServletContext getServletContext();
- 返回这个Servlet 上下文对象的引用。
4、getServletName
String getServletName();
- 返回Servlet实例的名字。即标签下的 xxx
如何获取ServletConfig的对象?
- 可通过调用Servlet接口的
getServletConfig()方法来返回ServletConfig对象。
3、ServletRequest 和ServletResponse 接口
- Servelt 由Servlet容器来管理,当客户请求到来时,容器创建一个
ServletRequest对象封装请求数据,同时创建一个ServletResponse对象封装响应数据。 - 这两个对象被容器作为Service()方法的参数传递给Servlet,Servlet利用 ServletRequest对象获取客户端请求的数据,利用ServletResponse 对象发送响应数据。
ServletRequest接口中定义了很多方法。 其中一些如下:
| 编号 | 方法 | 描述 |
|---|---|---|
| 1 | public Object getAttribute(String name) | 返回以 name 为名字的属性的值,如果这个属性不存在,就返回null |
| 2 | public Enumeration<String> getAttributeNames() | 返回请求中所有可用的属性的名字,如果没有,返回空的枚举集合。 |
| 3 | public void setAttribute(String name, Object object) | 在请求中保存名字为name 的属性。如果第二个参数 为null,相当于调用removeAttribute(name)。 |
| 4 | public void removeAttribute(String name) | 从请求中删除指定属性 |
| 5 | public String getParameter(String name) | 用于通过名称获取参数的值。 |
| 6 | public String[] getParameterValues(String name) | 返回一个包含给定参数名称的所有值的String数组。它主要用于获取多选列表框的值。 |
| 7 | java.util.Enumeration getParameterNames() | 返回所有请求参数名称的枚举。 |
| 8 | public int getContentLength() | 以字节为单位返回请求正文的长度,如果长度不知,返回-1 |
| 9 | public String getContentType() | 返回请求实体数据的网络媒体类型,如果未知则返回null。 |
| 10 | public ServletInputStream getInputStream() throws IOException | 返回用于读取请求正文中二进制数据的输入流。javax.servlet.ServletInputStream 是一个抽象类,继承自java.io.InputStream。 |
| 11 | public BufferedReader getReader() | 返回BufferedReader对象,以字符数据方式读取请求正文 |
| 12 | public abstract String getServerName() | 返回接收请求的服务器的主机名。 |
| 13 | public int getServletPort() | 返回接收请求的服务器的端口号 |
| 14 | public String getRemoteAddr() | 返回发送请求者的IP地址。 |
| 15 | public String getRemoteHost() | 返回发送请求者的主机名称。如果引擎不能或者选择不解析主机名(为了改善性能),这个方法会直接返回IP地址。 |
| 16 | public int getRemotePort() | 返回请求者的IP源端口 |
| 17 | public String getCharacterEncoding() | 返回此请求输入的字符集编码。 |
| 18 | public void setCharacterEncoding() | 覆盖请求正文中使用的字符编码的名字 |
| 19 | public RequestDispatcher getRequestDispatcher(String path) | 返回一个RequestDispatcher对象 |
| 20 | public String getLocalAddr() | 返回接收到请求的网络接口的IP地址 |
| 21 | public String getLocalName() | 返回接收到请求的IP接口的主机名 |
| 22 | public String getLocalPort() | 返回接收到请求的网络接口的IP端口号 |
接下来看ServletResponse接口里的方法:
| 编号 | 方法 | 描述 |
|---|---|---|
| 1 | public String getCharacterEncoding() | 返回响应中发送的正文使用的字符编码(MIME字符集) |
| 2 | public ServletOutputStream getOutputStream() | 返回一个ServletOutputStream 对象,用于在响应中写入二进制数据。javax.servlet.ServletOutputStream是一个抽象类,继承自java.io.OutputStream |
| 3 | public PrintWriter getWriter() | 返回 PrintWriter对象,用于发送字符文本到客户端。PrintWriter对象使用getCharacterEncoding()方法返回的字符编码。如果没有指定响应的字符编码方式,默认将使用ISO-8859-1。 |
| 4 | public void setContentLength(int length) | 对于HTTP Servlet ,在响应中,设置内容正文的长度,这个方法设置HTTP Content-Length 实体报头。 |
| 5 | public void setContentType(String type) | 设置要发送到客户端的响应的内容类型,此时响应应该还没有提交。给出的内容类型可以包括字符编码说明,例如:text/html;charset=UTF-8。 |
记忆技巧:从客户端和服务端交互过程思考,哪些对象应该是从请求对象中得到,哪些信息应该是用响应对象来设置!
4、ServletContext 接口
-
运行在Java虚拟机中的每个web应用程序都有一个与之相关的Servlet上下文。Java Servlet API 提供了一个ServletContext 接口用来表示上下文。Servlet 可以使用这个接口里的方法与 Servlet容器进行通信。
-
ServletContext的对象由Web容器在部署项目时创建。 该对象可用于从web.xml文件获取配置信息。 每个Web应用程序只有一个ServletContext对象。 如果有信息要共享给多个servlet使用,最好在web.xml文件中使用<context-param>元素提供它。
ServletContext接口部分方法。
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public String getInitParameter(String name) | 返回指定参数名称的参数值。 |
| 2 | public Enumeration getInitParameterNames() | 返回上下文的初始化参数的名称。 |
| 3 | public void setAttribute(String name,Object object) | 在应用程序范围内设置给定的对象。 |
| 4 | public Object getAttribute(String name) | 返回指定名称的属性。 |
| 5 | public Enumeration getInitParameterNames() | 返回上下文的初始化参数的名称,作为String对象的枚举。 |
| 6 | public void removeAttribute(String name) | 从servlet上下文中删除给定名称的属性。 |
| 7 | public String getRealPath(String path) | 返回一个符合URL路径格式的指定的虚拟路径的格式是:/dir/dir/filename.ext。 |
| 8 | public RequestDispatcher getRequestDispatcher(String uripath) | 返回RequestDispatcher对象 |
如何获取ServletContext接口的对象?
- 通过
ServletConfig接口的getServletContext()方法返回ServletContext对象。 - 通过
GenericServlet类的getServletContext()方法返回ServletContext对象。
5、GenericServlet 抽象类
之前我们是通过实现Servlet 接口来编写 Servlet 类,这需要实现 Servlet 接口中定义的五个方法,还需要维护手动维护 ServletConfig对象的引用。为了简化Servlet 的编写,在javax.servlet 包中提供了一个实现了 Servlet和 ServletConfig接口的抽象类 GenericServlet。
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
// NOOP
}
@Override
public void destroy() {
// NOOP by default
}
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
// NOOP by default
}
public void log(String msg) {
getServletContext().log(getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
@Override
public String getServletName() {
return config.getServletName();
}
}
可以看出其中大部分方法都是实现两个接口里的方法。如果我们需要编写一个通用的Servlet,只需要从GenericServlet类继承,并实现其中的抽象方法 service()。
在GenericServlet类中定义了两个重载的 init()方法,下面我们来看一下由来。子类实现了GenericServlet,但是如果子类需要自己的初始化操作怎么办?
解决方案:让子类去重写init(ServletConfig config) 方法
public void init(ServletConfig config) throws ServletException{
super.init(config);
System.out,println("子类自己的初始化操作");
}
万一开发者逻辑思维能力不高,往往就会出现一些问题:比如在重写父类的init(ServletConfig config)方法时忘记调用父类的初始化方法(错误的)。
SUN公司的科学家早就考虑到了这个问题,专门写了一个没有参数的用于子类做初始化的方法init(),并暴露给子类用来初始化操作。
6、HttpServlet 抽象类
我们编写的Servlet 主要应用于HTTP协议的请求和响应。为了快速开发应用于HTTP协议的Servlet类,Sun公司在 javax.servlet.http 包中给我们提供了一个抽象的类HttpServlet,HttpServlet类扩展了GenericServlet类并实现了Serializable接口。它提供了HTTP特定的方法,如:doGet,doPost,doHead,doTrace等。
定义
public abstract class HttpServlet extends GenericServlet
在HttpServlet 类中提供了两个重载的 service()方法:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
第一个service()方法是GenericServlet类中的service()方法的实现。在这个方法中,首先将req 和resp 对象转化为 HttpServletRequest(继承自ServletRequest接口)和HttpServletResponse(继承自ServletResponse接口)类型,然后调用第二个service方法,对客户请求进行处理。
针对HTTP1.1 中定义的 7种请求方法 GET、POST、HEAD、PUT、DELETE、TRACE和OPTIONS,HttpServlet分别提供了7个处理方法。
protected void doGet(HttpServletRequest req, HttpServletResponse res)//处理GET请求,它由Web容器调用。
protected void doPost(HttpServletRequest req, HttpServletResponse res)//处理POST请求,它由Web容器调用。
protected void doHead(HttpServletRequest req, HttpServletResponse res)//处理HEAD请求,它由Web容器调用。
protected void doOptions(HttpServletRequest req, HttpServletResponse res)//处理OPTIONS请求,它由Web容器调用。
protected void doPut(HttpServletRequest req, HttpServletResponse res)//处理PUT请求,它由Web容器调用。
protected void doTrace(HttpServletRequest req, HttpServletResponse res)//处理TRACE请求,它由Web容器调用。
protected void doDelete(HttpServletRequest req, HttpServletResponse res)//处理DELETE请求,它由Web容器调用。
7、HttpServletRequest 接口
定义:
public interface HttpServletRequest extends ServletRequest; //用来处理一个对Servlet的HTTP格式的请求信息。
常用方法:
1、String getMethod(); //返回请求方式:如GET/POST
2、String getRequestURI(); //返回请求行中的资源名字部分:如/test/index.html
3、StringBuffer getRequestURL(); //返回浏览器地址栏中所有的信息
4、String getContextPath(); //返回当前项目的上下文路径,该值以/开头(<Context/>元素的path属性值.)
5、String getRemoteAddr(); //返回发出请求的客户机的IP地址
6、String getHeader(String name);//返回指定名称的请求头的值。
//获取请求参数的方法:
1、String getParameter(String name); //根据参数名获取单个参数值。
2、String[] getParameterValues(String name); //根据参数名获取多个参数值。
3、Enumeration<String> getParameterNames(); //返回包含所有参数名的Enumeration对象。
4、Map<String,String[]> getParameterMap(); //返回所有的参数名和值所组成的Map对象。
public java.lang.String getQueryString(); //返回请求URL 中在路径后的查询字符串。如果在URL中没有查询字符串,该方法返回null。例如,有如下的请求URL:http://localhost:8080/ch02/logon?action=logon调用getOueryString()方法将返回action=logon。
注意
ServletConfig中的getInitParameter方法是用于获取Servlet配置的初始化参数,该参数是程序员在开发的时候配置在web.xml中的HttpServletRequest中的getParamter方法是用于获取客户端发送过来的参数,该参数是用户在发出请求携带的。
想要更好的理解
HttpServletRequest和HttpServletResponse的使用,请结合HTTP协议看。
扩展
因为Request代表请求,所以我们可以通过该对象分别获得HTTP请求的请求行,请求头和请求体。
- 通过request获得请求行
String getMethod() //获得客户端的请求方式
//获得请求的资源:
String getRequestURI() //返回请求行中的资源名字部分:如/test/index.html
StringBuffer getRequestURL() ////返回浏览器地址栏中所有的信息
String getContextPath() //返回当前项目的上下文路径,该值以/开头(<Context/>元素的path属性值.)
String getQueryString() // 返回这个请求URL所包含的查询字符串。一个查询字串符在一个URL中由一个“?”引出。如果没有查询字符串,这个方法返回空值( 即get提交url地址后的参数字符串)
- 通过request获得请求头
long getDateHeader(String name) //返回指定的请求头域的值,这个值被转换成一个反映自1970-1-1日(GMT)以来的精确到毫秒的长整数。如果头域不能转换,抛出一个IllegalArgumentException。如果这个请求头域不存在,这个方法返回-1。
String getHeader(String name)//返回一个请求头域的值。(译者注:与上一个方法不同的是,该方法返回一个字符串)如果这个请求头域不存在,这个方法返回-1。
Enumeration getHeaderNames()//该方法返回一个String对象的列表,该列表反映请求的所有头域名。有的引擎可能不允许通过这种方法访问头域,在这种情况下,这个方法返回一个空的列表。
int getIntHeader(String name)// 返回指定的请求头域的值,这个值被转换成一个整数。如果头域不能转换,抛出一个IllegalArgumentException。如果这个请求头域不存在,这个方法返回-1。
- 通过request获得请求体
/*请求体中的内容是通过post提交的请求参数,格式是:
username=zhangsan&password=123&hobby=football&hobby=basketball
key ---------------------- value
username [zhangsan]
password [123]
hobby [football,basketball] */
//以上面参数为例,通过一下方法获得请求参数:
String getParameter(String name)
String[] getParameterValues(String name)
Enumeration getParameterNames()
Map<String,String[]> getParameterMap()
注意:get请求方式的请求参数 上述的方法一样可以获得。
乱码原因:
由于计算机中的数据都是以二进制形式存储的,因此,当传输文本时,就会发生字符和字节之间的转换。字符与字节之间的转换是通过查码表完成的,将字符转换成字节的过程称为编码,将字节转换成字符的过程称为解码,如果编码和解码使用的码表不一致,就会导致乱码问题。
7.1 Request中文乱码问题的解决方法
- 在Tomcat服务器中,对于所有请求方式(GET/POST)的默认解码的方式都是ISO-8859-1
- 浏览器使用的参数编码方式的UTF-8编码的,而服务器使用的是ISO-8859-1的方式来解码,结果中文肯定乱码
解决方案:
- 把字符串用ISO-8859-1的方式编码还原成byte数组
byte[] data = username.getBytes("ISO-8859-1");
- 把该byte数组,使用UTF-8的方式来解码
username = new String(data, "UTF-8");
但是,如果需要操作的参数过多,这个代码又要写N次。因此
针对GET方式:
-
以后开发中get方式一般不传参数,尤其不传中文参数
-
在
server.xml文件中,配置默认处理URI的编码即可
针对POS方式:
在获取任意参数之前,设计请求体内容的编码 -> UTF-8
req.setCharacterEncoding("UTF-8");
8、HttpServletResponse 接口
-
ServletResponse接口:该接口的对象就表示响应对象,可以对请求作出响应操作
-
HttpServletResponse接口: 该接口对象可以处理带有HTTP类型的响应操作
定义
public interface HttpServletResponse extends ServletResponse // 描述一个返回到客户端的HTTP回应。这个接口允许Servlet程序员利用HTTP协议规定的头信息。
1、public void addCookie(Cookie cookie);//在响应中增加一个指定的cookie。可多次调用该方法以定义多个cookie。为了设置适当的头域,该方法应该在响应被提交之前调用。
2、public boolean containsHeader(String name);//检查是否设置了指定的响应头。
3、public String encodeRedirectURL(String url);//对sendRedirect方法使用的指定URL进行编码。如果不需要编码,就直接返回这个URL。之所以提供这个附加的编码方法,是因为在 redirect的情况下,决定是否对URL进行编码的规则和一般情况有所不同。所给的URL必须是一个绝对URL。相对URL不能被接收,会抛出一个 IllegalArgumentException。所有提供给sendRedirect方法的URL都应通过这个方法运行,这样才能确保会话跟踪能够在所有浏览器中正常运行。
4、public String encodeURL(String url);//对包含session ID的URL进行编码。如果不需要编码,就直接返回这个URL。Servlet引擎必须提供URL编码方法,因为在有些情况下,我们将不得不重写URL,例如,在响应对应的请求中包含一个有效的session,但是这个session不能被非URL的(例如cookie)的手段来维持。所有提供给Servlet的URL都应通过这个方法运行,这样才能确保会话跟踪能够在所有浏览器中正常运行。
5、public void sendError(int statusCode) throws IOException;
public void sendError(int statusCode, String message) throws IOException;//用给定的状态码发给客户端一个错误响应。如果提供了一个message参数,这将作为响应体的一部分被发出,否则,服务器会返回错误代码所对应的标准信息。调用这个方法后,响应立即被提交。在调用这个方法后,Servlet不会再有更多的输出。
6、public void sendRedirect(String location) throws IOException;//使用给定的路径,给客户端发出一个临时转向的响应(SC_MOVED_TEMPORARILY)。给定的路径必须是绝对URL。相对URL将不能被接收,会抛出一个IllegalArgumentException。这个方法必须在响应被提交之前调用。调用这个方法后,响应立即被提交。在调用这个方法后,Servlet不会再有更多的输出。
7、public void setDateHeader(String name, long date);//用一个给定的名称和日期值设置响应头,这里的日期值应该是反映自1970-1-1日(GMT)以来的精确到毫秒的长整数。如果响应头已经被设置,新的值将覆盖当前的值。
8、public void setHeader(String name, String value);//用一个给定的名称和域设置响应头。如果响应头已经被设置,新的值将覆盖当前的值。
9、public void setIntHeader(String name, int value);//用一个给定的名称和整形值设置响应头。如果响应头已经被设置,新的值将覆盖当前的值。
10、public void setStatus(int statusCode);//这个方法设置了响应的状态码,如果状态码已经被设置,新的值将覆盖当前的值。
注意:
-
ServletOutputStream
getOutputStream() : 获取字节输出流对象,该对象可以把数据输出到浏览器,文件下载时使用 -
PrintWriter
getWriter() : 获取和浏览相关的打印流对象 推荐使用
以上两个流对象,只能使用1个,不然报错。

8.1 Response中文乱码问题的解决方法
- 告诉浏览器数据的MIME类型
resp.setContentType("text/html");
- 告诉浏览器内容的编码
resp.setCharacterEncoding("utf-8");
以上两行代码可以合并,告诉浏览器数据的MIME类型和编码
resp.setContentType("text/html;charset=utf-8");
注意:该操作必须在获取输出流对象之前操作,否则无效!
9、ResquestDispatcher 接口
ResquestDispatcher对象由Servlet容器创建,用于封装一个由路径识别的服务器资源。利用ResquestDispatcher对象,可以把请求转发给其他的 Servlet 或 JSP页面。
在RequestDispatcher接口中定义了两种方法。它们分别是 :
| 编号 | 方法 | 描述 |
|---|---|---|
| 1 | public void forward(ServletRequest request,ServletResponse response) | 将servlet的请求转发到服务器上的另一个资源(servlet,JSP文件或HTML文件)。 |
| 2 | public void include(ServletRequest request,ServletResponse response) | 在响应中包含资源的内容(servlet,JSP页面或HTML文件)。 |
下面来看两种方法的区别:

如上图所示,第一个 servlet转发请求到第二个 servle的响应发送给客户端。 第一个 servlet的响应不会显示给用户。

如上图所示,第二个 servlet的响应将包含在发送给客户端的第一个servlet的响应中。
3.3 Servlet 的继承体系
3.4 Servlet 的作用域对象
作用:可以共享数据。
-
request: 每一次请求的请求对象,如果在同一个请求内要共享数据,就可以使用请求转发来共享数据。
-
session: 每一次会话对象,如果在一次会话中,多个请求之间共享数据,就可以使用session来共享数据。
-
application
- 整个web应用描述成的对象,一个web应用就是一个application对象,如果需要在多个会话中共享数据,就可以使用application对象。
- web容器在启动的时候,它会为每个web程序都创建一个对应的ServletContext对象,它代表了当前的web应用
| Servlet的作用域对象名称 | 对象的类型 |
|---|---|
| request | HttpServletRequest |
| session | HttpSession |
| application(servletContext) | ServletContext |
3.5 Servelt 生命周期
Servlet 运行在 Servlet容器中,其生命周期由容器来管理。Servlet 的生命周期通过Servlet接口中的init()、service()、destory()方法来表示。
Servlet 的生命周期包含了下面四个阶段:
1、加载实例化
- Servlet 容器负责加载和实例化Servlet。当Servlet 容器启动时,或者在当Servlet 容器接收到servlet的第一个请求时,类加载器负责加载Servlet 类。
- 由于容器是通过反射来创建Servlet实例,调用的是Servlet的默认构造方法,因此我们在编写Servlet类时,要提供公共无参构造器。
2、初始化
- **在Servlet 实例化后,容器会调用 Servlet 的
init()方法来初始化这个对象。**初始化的目的是让Servlet 对象在处理客户端请求前完成一些初始化的工作,如建立数据库连接,获取配置信息等 - 对于每个Servlet实例,
init()方法只被调用一次。 - 在初始化期间,可以通过ServletConfig 对象从 Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。
3、请求处理
- 每当接收到servlet的请求时,Web容器都会调用
service方法。如果servlet未初始化,则遵循上述前三个步骤,然后调用service方法。 如果servlet被初始化,它调用service方法。 请注意,servlet仅初始化一次。
4、服务终止
- 从服务中删除servlet实例之前,Web容器调用
destroy方法。它使servlet有机会清理所有资源,例如内存,线程等
在整个 Servlet 的生命周期中,创建Servlet实例、调用实例的 init() 和destory() 方法都只进行一次,当初始化完成后, Servlet容器会将该实例保存在内存中,通过调用它的service() 方法,为接收到的请求服务。
Servlet 整个生命周期过程的UML时序图如下:
提示:如果需要让 Servlet容器在启动时就加载 Servlet,可以在
web.xml文件配置 元素。
3.6 Servlet 的执行流程
- Tomcat 解析请求为上下文路径(path)和资源的名称(url-pattern)
- 根据上下文路径匹配
server.xml文件中的path的值,在根据docBase的值定位到项目的根路径 - 根据资源名称匹配
WEB-INF/web.xml中的url-pattern,获取到Servlet的全限定名 - 判断是否存在这个类的缓存
- 如果没有,就通过反射创建servlet对象,放入缓存。即加载实例化Servlet、初始化
init()、请求处理service() - 如果有则调用
service()方法传递请求和响应对象。
- 如果没有,就通过反射创建servlet对象,放入缓存。即加载实例化Servlet、初始化
- Web容器在需要删除servlet时调用
destroy方法,例如:在停止服务器或取消部署项目时。
3.7 servlet 的匹配顺序
当一个url与多个servlet的匹配规则可以匹配时,则按照 “ 精确路径 > 最长路径>扩展名”这样的优先级匹配到对应的servlet。举例如下:
1、精确路径匹配例
- 例子:比如servletA 的url-pattern为 /test,servletB的url-pattern为 /* ,这个时候,如果我访问的url为http://localhost/test ,这个时候容器就会先进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的 servlet了。
2、最长路径匹配
- 例子:servletA的url-pattern为
/test/*,而 servletB的url-pattern为/test/a/*,此 时访问http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB。
3、扩展匹配
- 如果url最后一段包含扩展,容器将会根据扩展选择合适的servlet。例子:servletA的url-pattern:*.action
4、如果前面三条规则都没有找到一个servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default servlet,则容器会将请求丢给default servlet(“/” 是用来定义default servlet映射的,default servlet是tomcat内置的)
3.8 url-pattern详解
< url-pattern>/</url-pattern> <!--会匹配到/login这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url-->
< url-pattern>/*</url-pattern> <!--会匹配所有url:路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)-->
首先无论是 / 还是 /*,springMVC都会拦截所有请求,
- 当使用 / 时,会覆盖默认处理静态资源的Servlet,根据上面的匹配顺序,会匹配到/login这样的路径型url,不会匹配到模式为 .jsp 这样的后缀型url。说到为什么JSP页面的请求并不会命中这个servlet,那是因为servlet容器内建的JSP servlet将会被调用,而这个容器内建的JSP servlet已经默认地映射在了*.jsp上。
- 当使用 /* 时,会匹配所有url:路径型的和后缀型的url(包括
/login,*.jsp,*.js和*.html等),会出现返回 jsp视图 时再次进入spring的DispatcherServlet 类,导致找不到对应的controller所以报404错。
在项目中我们定义的web.xml是访问动态资源的。
在tomcat内置的有两种servlet,一种是DefaultServlet,另一种是JspServlet。
<!-- 默认JSP Servlet -->
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<!--默认default Servlet-->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
-
default的servlet是解读静态页面的,这种方法使用映射规则的是
-
<url-pattern>/</url-pattern>
-
-
而jsp的servlet是解读动态界面的,这个方法使用映射规则的是
-
<url-pattern>*.jsp</url-pattern> 和 <url-pattern>*.jspx</url-pattern>
-
注意:
<url-pattern>/</url-pattern>
当我们配置自己的Servlet映射时,使用这种方式会覆盖默认的DefaultServlet,使原本访问静态资源变成了访问动态资源,导致无法访问静态页面。因此在springmvc.xml文件中添加 < mvc:default-servlet-handler >,将静态资源交给默认的Servlet去处理,可以避免这个问题。
3.9 forward() 和 sendRedirect()方法的区别
从图中可以看出调用 sendRedict() 方法,实际上是告诉浏览器 Servlet2 所在位置,让浏览器重新访问Servlet2。整个过程是对用户是透明的,浏览器自动完成。且浏览器地址栏显示的URL是重定向之后的URL。
从图中可以看出调用forward()方法,对浏览器来说是透明的,浏览器并不知道为其服务的Servlet已经换成Servlet2了,它只知道发出一个请求,获得了一个响应。且浏览器地址栏显示的URL始终是原始请求的URL。
sendRedirect() 方法回容forward() 方法还有一个区别,那就是sendRedirect()方法不但可以在位于同一主机上的不同Web应用程序之间重定向,而且可以将客户端重定向到其他服务器的Web应用程序资源。
转发和重定向的区别总结:
1、请求次数不同,重定向两次,转发一次
2、重定向时地址栏会发生变化,而转发时地址栏不会发生变化
3、重定向两次请求不共享数据,转发一次请求共享数据
4、重定向时的网址可以是任何网址,转发的网址必须是本站点的网址
扩展:表单重复提交
表单重复提交是三种方式:
- 用户提交表单后,刷新地址栏
- 解决:重定向
- 网络延迟,用户再次点击提交按钮
- 禁用按钮
- 用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交
- token
3.10 cookie 和 session
1、会话跟踪
在 Java Servlet API中,javax.servlet.http.HttpSession接口封装了Session的概念。Servlet容器提供了这个接口的实现。当请求一个会话的时候,Servlet容器就会创建一个HttpSession对象,这个对象可以用来保存客户的状态信息。Servlet容器为 HttpSession对象分配一个唯一的Session ID,将其作为Cookie发送给浏览器,浏览器在内存中保存这个cookie。当客户再次发送HTTP请求时,浏览器将Cookie随请求一起发送,Servlet容器从请求对象中读取Session ID,然后根据 Session ID找到对应的HttpSession对象,从而获得客户的状态信息。
2、HttpSession 接口
如何获取HttpSession对象?
HttpServletRequest接口提供了两种获取HttpSession对象的方法:
public HttpSession getSession():返回与此请求相关联的当前会话,或者如果请求没有会话,则创建一个会话。public HttpSession getSession(boolean create): 返回与此请求相关联的当前HttpSession,如果没有当前会话,并且create的值为true,则返回一个新会话。
HttpSession接口的常用方法
-
public Object getAttribute(String name) -
public Enumeration getAttributeNames() -
public void removeAttribute(String name) -
public void setAttribute(String name, Object value)
上面四个方法用于在 HttpSession对象中读取、移除和设置属性,利用这些方法,可以在Session中维护客户的状态信息。
-
public String getId(): 返回一个包含唯一标识符值的字符串。 -
public long getCreationTime(): 返回创建此会话的时间,以1970年1月1日GMT格林尼治时间以来的毫秒为单位。 -
public long getLastAccessedTime(): 返回客户端发送与此会话相关联的请求的最后一次,为1970年1月1日GMT以来的毫秒数。 -
public void invalidate():使此会话无效,然后取消绑定绑定到该对象的任何对象。 -
public boolean isNew():如果客户端还不知道这个Session或者客户端没有选择加入Session,那么这个方法将返回true。例如,服务器使用基于Cookie的Session,而客户端禁用了Cookie,那么对每一个请求,Session都是新的。
3、cookie和session的区别
cookie:是客户端技术,因为HTTP请求时无状态的,所以服务器会把数据以cookie的形式返回给浏览器,当再次访问的时候浏览器就带着cookie来访问。
session:是服务端技术,服务端会为每个用户都创建一个独享的session对象,服务端将JSESSIONID通过的cookie返回给浏览器,当再次访问时,浏览器就根据JSESSIONID去session对象取数据,安全可靠。
3.11 Servlet 的异常处理机制
1、声明式异常处理
声明式异常处理是在web.xml文件中声明对各种异常的处理办法。这是通过<erreo-page>元素来声明的。
error-code元素指定HTTP错误代码exception-type元素指定Java异常类的完整限定名location元素指定用于响应HTTP错误代码或者Java异常的资源的路径,该路径是相对于Web应用程序的根路径的位置,必须以斜杠(/) 开头。
HTTP 错误代码的处理
当我们访问404,找不到资源的时候,就会跳转到error.html页面。
<error-page>
<error-code>404</error-code>
<location>/error.html</location>
</error-page>
除此之外,我们还可以写一个专门用来处理HTTP错误代码的类,例如 HttpErrorHandleServlet来处理。在web.xml文件中配置该类
<servlet>
<servlet-name>HttpErrorHandleServlet</servlet-name>
<servlet-class>com.zxc.Controller.HttpErrorHandleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HttpErrorHandleServlet</servlet-name>
<url-pattern>/HttpErrorHandle</url-pattern>
</servlet-mapping>
<error-page>
<error-code>404</error-code>
<location>/HttpErrorHandle</location>
</error-page>
当发生HTTP错误的时候,Servlet容器会自动将HTTP错误代码作为javax.servlet.error.status_code属性的值,保存到请求对象中。为了帮助进行错误处理的Servlet分析问题,以及生成详细的响应,Servlet容器在将请求转发给错误页面之前,会在请求中设置某些有用的属性。
Java异常的处理
利用<error-page> 元素还可以声明对程序中产生的Java异常处理。
<servlet>
<servlet-name>ExceptionHandleServlet</servlet-name>
<servlet-class>com.zxc.Controller.ExceptionHandleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ExceptionHandleServlet</servlet-name>
<url-pattern>/ExceptHandle</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.io.FileNotFoundException</exception-type>
<location>/ExceptHandle</location>
</error-page>
2、程序式异常处理
就是在Web程序中利用 try-catch 语句来捕获异常,并进行处理。
3.12 Servlet 的线程安全
1、多线程的 Servlet模型
Servlet 容器只产生一个Servlet实例,如果多个客户请求同时访问这个 Servlet,Servlet容器将采用多线程。容器维护了一个线程池来服务请求。
- 线程池实际上等待执行代码的一组线程,这些线程叫做工作线程(Worker Thread)
- Servlet容器使用一个调度线程(Dispatcher Thread)来管理工作线程
当容器接受到请求时,调度线程从线程池中取一个工作线程,将请求传递给该线程,然后由其执行service()方法。
当这个线程正在执行的时候,容器收到了另外一个请求,调度者线程将从池中选取另一个线程来服务新的请求。要注意的是,Servlet容器并不关心这第二个请求是访问同Servlet还是另一个Servlet。因此,如果容器同时收到访问同一个Servlet的多个请求,那么这个Servlet 的 service()方法将在多个线程中并发地执行。图7-2显示了两个工作者线程都在执行同一个Servlet的service()方法。
2、总结
Servlet 规范定义,在默认情况下,Servlet是多线程的,一个 Servlet实例同时在多个线程中执行(单实例多线程),并发地处理多个客户端请求。因为 Servlet 是多线程的,所以在开发Servlet时,要注意线程安全的问题。
-
变量的线程安全问题:本地变量总是线程安全的,实例变量和类变量不是线程安全的
-
属性的线程安全问题:请求对象的属性访问是线程安全的,Session对象和上下文对象的属性访问不是线程安全的
建议:
- 尽可能地在Servlet 中只使用本地变量。
- 应该只使用只读的实例变量和静态变量。
- 不要在 Servlet 中创建自己的线程。
- 修改共享对象时,一定要使用同步,尽可能地缩小同步代码的范围,不要直接在service()方法或doXXX(方法上进行同步,以免影响性能。
- 如果在多个不同的Servlet中,要对外部对象(例如,文件)进行修改操作,一定要加锁,做到互斥的访问。
3.13 Filter
- 过滤器,就是在源数据和目的数据之间起过滤作用的中间组件,类比于生活中的污水净化器。
- 而对于Web应用程序来说,过滤器是一个驻留在服务器端的Web组件,它可以截取客户端和资源之间的请求和响应信息,并对这些信息进行过滤。
1、拦截器与过滤器的区别
1、拦截器是基于java的反射机制的,而过滤器是基于函数的回调。
2、拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
3、拦截器只对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4、拦截器可以访问action上下文、值、栈里面的对象,而过滤器不可以。
5、在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
6、拦截器可以获取IOC容器中的各个bean,而过滤器不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑
2、过滤器的使用
- 记录所有传入的请求
- 记录来自标计算机的IP地址的请求
- 转变/转换
- 数据压缩
- 加密和解密
- 输入验证等
3、过滤器的API
与过滤器开发相关的接口与类都包含在javax.servlet和javax.servlet.http包中,主要有下面的接口和类:
javax.servlet.Filter接口javax.servlet.FilterConfig接口javax.servlet.FilterChain接口javax.servlet.ServletRequestWrapper类javax.servlet.ServletResponseWrapper类javax.servlet.http.HttpServletRequestWrapper类javax.servlet.http.HttpServletResponseWrapper类
与开发 Servlet要实现javax.servlet.Servlet 接口类似,开发一个过滤器,必须要实现javax.servlet.Filter接口,并提供一个公共无参构造器。Filter接口为过滤器提供了生命周期方法如下。
| 方法 | 描述 |
|---|---|
public void init(FilterConfig config) | init()方法只被调用一次,用于初始化过滤器。容器在调用该方法时,向过滤器传递FilterConfig对象,FilterConfig是用法和ServletConfig类似 |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 该方法类似于 Servlet接口的Service()方法,当用户请求过滤器所映射到资源时,会调用doFilter()方法,它用于执行过滤任务。在特定操作完成后,可以调用chain.doFilter(res, resp)将请求传递给下一个过滤器。也可以直接向客户端返回响应信息,或者利用forward()和include()方法,以及sendRedirect()方法将请求转向到其他资源。需要注意的是,这个方法的请求和响应参数的类型是ServletRequest和ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。 |
public void destroy() | 当过滤器从服务中取出时,调用此方法(仅一次)。 |
4、FilterConfig接口
FilterConfig接口与ServletConfig接口方法差不多,不做过多阐述。
5、FilterChain 接口
| 方法 | 描述 |
|---|---|
public void doFilter(ServletRequest request,ServletResponse response) | 调用该方法将使过滤器链中的下一个过滤器被调用。如果调用该方法的过滤器是链中最后一个过滤器,那么目标资源被调用。 |
6、四个包装类
HttpServletRequest 类没有提供的对请求信息进行修改的 setXXX()方法,而HttpServletResponse 类也没有提供得到响应数据的方法。也就是虽然过滤器可以截取到请求和响应对象,但是却无法直接使用这两个对象对它们的回数据进行替换。
虽然不能直接修改,但是可以利用请求和响应的包装类,来间接改变请求和响应的信息。在Servlet规范中,定义了 4 个包装类:ServletRequestWrapper、ServletResponseWrapper、HttpServletRequestWrapper、HttpServletResponseWrapper。它们都实现了请求和响应的接口。
它们在构造方法中接受真正的请求或响应对象,利用该对象的方法来完成自己需要实现的方法。包装类是装饰设计模式的运用。我们只需要编写一个包装类的子类,然后覆盖想要修改的方法就可以了。
7、过滤器执行顺序
8、案例:字符编码过滤
<!--配置字符编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
9、其他案例
- Servlet身份验证过滤器
- 《Servlet JSP深入详解》16.5 对用户进程统一验证的过滤器
- 《Servlet JSP深入详解》16.6 对请求和响应数据进行替换的过滤器
- 《Servlet JSP深入详解》16.7 对响应内容进行压缩的过滤器
3.14 Listener
- Listener就是监听器,我们在JavaSE开发或者Android开发时,经常会给按钮加监听器,当点击这个按钮就会触发监听事件,调用onClick方法,本质是方法回调。
- Servlet API中定义了8个监听器接口,可以用监听
ServletContext、HttpSession和ServletRequest对象的生命周期事件,以及这些对象的属性改变事件。
应用域监听:
1、ServletContext(监听Application)
- 生命周期监听:
ServletContextListener,它有两个方法,一个在出生时调用,一个在死亡时调用;
void contextInitialized(ServletContextEvent sce)://当Web应用程序初始化进程正开始时,Web容器调用这个方法。该方法将在所有的过滤器和Servlet初始化之前被调用。
void contextDestroyed(ServletContextEvent sce)://当Servlet上下文将要被关闭时,Web容器调用这个方法。该方法将在所以的 Servle和过滤器销毁之后被调用。
Servlet 容器通过
ServletContextEvent对象来通知实现了ServletContextListener接口的对象,该对象可以利用ServletContextEvent对象得到ServletContext对象。
javax.servlet.ServletContextEvent类除了构造方法外,只定义了一个方法,就是public ServletContext getServletContext();该方法用于得到ServletContext对象。
- 属性监听:
ServletContextAttributeListener,它有三个方法,一个在添加属性时调用,一个在替换属性时调用,最后一个是在移除属性时调用。
void attributeAdded(ServletContextAttributeEvent event)://添加属性时;
void attributeReplaced(ServletContextAttributeEvent event)://替换属性时;
void attributeRemoved(ServletContextAttributeEvent event)://移除属性时;
2、HttpSession(监听Session)
- 生命周期监听:
HttpSessionListener,它有两个方法,一个在出生时调用,一个在死亡时调用;
void sessionCreated(HttpSessionEvent se)://创建session时
void sessionDestroyed(HttpSessionEvent se)://销毁session时
Servlet 容器通过
HttpSessionEvent对象来通知实现了HttpSessionListener接口的对象,该对象可以利用HttpSessionEvent对象得到HttpSession对象。
javax.servlet.http.HttpSessionEvent类除了构造方法外,只定义了一个方法,就是public HttpSession getSession();该方法用于得到HttpSession对象。
- 属性监听:
HttpSessioniAttributeListener,它有三个方法,一个在添加属性时调用,一个在替换属性时调用,最后一个是在移除属性时调用。
void attributeAdded(HttpSessionBindingEvent event)://添加属性时;
void attributeReplaced(HttpSessionBindingEvent event)://替换属性时
void attributeRemoved(HttpSessionBindingEvent event)://移除属性时
| 监听器接口 | 方法 | 说明 |
|---|---|---|
javax.servlet.http.HttpSessionActivationListener | sessionWillPassivate sessionDidActivate | 实现这个接口,如果绑定到Session中,当Session被钝化或激活时,Servlet容器将通知该对象 |
javax.servlet.http.HttpSessionBindingListener | valueBound valueUnbound | 如果想让一个对象在绑定到Session中或者从Session中被删除时得到通知,那么可以让这个对象实现该接口 |
HttpSessionAttributeListener和 HttpSessionBindingListener接口的主要区别是:
- 前者用于监听Session中何时添加、删除或者替换了某种类型的属性,
- 而后者是由属性自身来实现,以便属性知道它何时添加到一个Session中,或者何时从Session中被删除
3、ServletRequest(监听Request)
- 生命周期监听:
ServletRequestListener,它有两个方法,一个在出生时调用,一个在死亡时调用;
void requestInitialized(ServletRequestEvent sre)://创建request时
void requestDestroyed(ServletRequestEvent sre)://销毁request时
- 属性监听:
ServletRequestAttributeListener,它有三个方法,一个在添加属性时调用,一个在替换属性时调用,最后一个是在移除属性时调用。
void attributeAdded(ServletRequestAttributeEvent srae)://添加属性时
void attributeReplaced(ServletRequestAttributeEvent srae)://替换属性时
void attributeRemoved(ServletRequestAttributeEvent srae)://移除属性时
4、案例:HttpSessionBindingListener
如果一个对象实现了该接口,当该对象被绑定到 session中 或者从 session中删除时,容器会通知该对象,而该对象接收到通知时可以做一些初始化或清除状态的操作。
Servlet 容器通过HttpSessionBindingEvent对象来通知实现了 HttpSessionBindingListener 接口的对象,该对象可以利用 HttpSessionBindingEvent对象得到HttpSession对象。
HttpSessionBindingEvent类如下:
public class HttpSessionBindingEvent extends HttpSessionEvent {
/* The name to which the object is being bound or unbound */
private final String name;
/* The object is being bound or unbound */
private final Object value;
public HttpSessionBindingEvent(HttpSession session, String name) {
super(session);
this.name = name;
this.value = null;
}
public HttpSessionBindingEvent(HttpSession session, String name,
Object value) {
super(session);
this.name = name;
this.value = value;
}
//上面两个构造方法构造一个事件对象,当一个对象被绑定到Session中或者从Session中被删除时,用这个事件对象来通知它。
@Override
public HttpSession getSession() {
return super.getSession();
}
public String getName() {
return name;
}
public Object getValue() {
return this.value;
}
}
5、案例:HttpSessionBindingListener 统计在线人数!
利用HttpSessionBindingListener 接口,编写一个统计在线人数。
- 当一个用户登录后,显示当前在线人数和用户名单。当一个用户退出登录或者Session超时值发生,从在线用户名单删除该用户。
- 这个功能注意利用实现了
HttpSessionBindingListener接口的对象,当这个对象绑添加到session中或从session中删除时,更新当前在线用户名单。
《Servlet JSP深入详解》15.4 在线人数统计程序
6、案例:ServletContextListener在Spring中的应用
Spring容器可以显示的实例化一个Spring IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件的路径")
也可以在web.xml配置文件中注册Spring IOC容器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
其中的监听器类【org.springframework.web.context.ContextLoaderListener】实现了ServletContextListener接口,能够监听ServletContext的生命周期中的“初始化”和“销毁”。这个监听器是Spring提供的。
当ServletContext初始化后,Spring IOC容器是如何初始化的呢?
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
--------------------------------------------------------重点关注下面这里哦!--------------------------------
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
--------------------------------------------------------重点关注上面这里哦!--------------------------------
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
从代码中可以看出是通过ContextLoaderListener父类ContextLoader里的方法 this.initWebApplicationContext(event.getServletContext());来进行初始化 Spring IOC容器的
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
} catch (RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}
}
}
此时SpringIOC容器已被实例化。
总结:
- 当Servlet容器启动时,ServletContext对象被初始化,然后Servlet容器调用web.xml中注册的监听器的public void contextInitialized(ServletContextEvent event)方法
- 在该方法中,调用了this.initWebApplicationContext(event.getServletContext())方法,在这个方法中实例化了Spring IOC容器。即ApplicationContext对象。
- 因此,当ServletContext创建时我们可以创建applicationContext对象,当ServletContext销毁时,我们可以销毁applicationContext对象。这样applicationContext就和ServletContext“共生死了”。
3.15 国际化 i18n
国际化(Internationalization)是使程序在不做任何修改的情况下,就可以在不同的国家地区和不同的语言环境下,按照当地的语言和格式习惯显示字符。国际化又被称为I18N,因为国际化的英文的是Internationlization,它以I开头 ,N结尾,中间有18个字母。
在Java中编写国家化程序主要通过两个类来完成,java.util.Locale类 和java.util.ResourceBundle抽象类。
Locale类用于提供本地信息,通常称为语言环境。不同语言、不同国家和地区采用不同的locale对象来表示。ResourceBundle类称为资源包,包含了特定于语言环境的资源对象。
1、Locale类 语言环境
java.util.Locale 类的常用构造方法如下:
public Locale(String language)public Locale(String language, String country)
其中 language表示语言,取值由ISO-639定义的小写的、两个字母组成的代码。
| 语言 | 代码 |
|---|---|
| 汉语(Chinese) | zh |
| 英语(English) | en |
| 德语(German) | de |
| 法语(French) | fr |
| 日语(Japanese) | ja |
| 朝鲜语(Korean) | ko |
其中 country 表示国家和地区,取值由ISO-3166定义的大写的、两个字母组成的代码。
| 国家(地区) | 代码 |
|---|---|
| 中国(China) | CN |
| 美国(United States) | US |
| 英国(Great Britain) | GB |
| 德国(Germany) | DE |
| 日本(Japan) | JP |
| 韩国(Korea) | KR |
例如
- 应用于中国的
Locale
Locale locale = new Locale("zh", "CN");
- 应用于美国的
Locale
Locale locale = new Locale("en", "US");
- 英语于应该的
Locale
Locale locale = new Locale("en", "GB");
为了简化,Locale类中定义了许多Locale对象常量。
应用国家或地区的Locale对象有:
- Locale.CANADA
- Locale.CHINA
- Locale.FRANCE
- Locale.JAPAN
- Locale.US
- Locale.HK
- Locale.TAIWAN
应用于语言的Locale对象(只设定语言,没有设定国家和地区)
- Locale.CHINESE
- Locale.ENGLISH
- Locale.JAPANSE
另外,在Locale类中,还定义了一个静态的方法getDefault()用于获得本地系统默认的Locale对象。
2、ResourceBundle类 资源包
要获取某个资源包,可以调用java.util.ResourceBundle类中的静态方法getBundle()
-
public static final ResourceBundle getBundle(String baseName);根据基名得到资源包,使用系统默认的Locale对象。 -
public static final ResourceBundle getBundle(String baseName, Locale locale);根据基名和Locale对象得到资源包。
利用getBundle() 方法可以得到对应于某个Locale对象的资源包,然后就可以利用 ResourceBundle类的getString()方法得到相应语言版本的字符串。
public final String getString(String key)
为了简化资源包的简写,从ResourceBundle类派生出来了两个资源类:ListResourceBundle和 PropertyResourceBundle。
-
使用
ListResourceBundle类,需要编写自己的资源包继承此类,然后将所有的数据放入一个对象数组即可。 -
使用
PropertyResourceBundle类,把数据放在属性文件中(properties),根据选择不同的国家/地区,来读取不同的配置文件,该配置文件名称必须符合规则:基本名称_语言_国家/地区.properties
加载资源,可以调用 ResourceBundle 类的静态方法getBundle(),getBundle()方法首先去加载资源类;如果没有成功,则试着去加载属性资源文件,如果成功,则创建一个新的PropertyResourceBundle对象。
案例
ResourceBundle bundle = ResourceBundle.getBundle("app",Locale.CHINA);
String username = bundle.getString("username");
String password = bundle.getString("password");
System.out.println(username);
System.out.println(password);
==注意: 此处要将配置文件方在src目录下!!!==不然报 java.util.MissingResourceException: Can‘t find bundle for base name app, locale zh_CN
输出:
3、MessageFormat消息格式化
String msg = "我是{1},你是{0},他是{3},它是{2}";
msg = MessageFormat.format(msg, "one","two","three","four");
System.out.println(msg);
//output
//我是two,你是one,他是four,它是three
format()方法参数的顺序,是和占位符的数字顺序相对应的,而不是与占位符出现在消息文本的顺序对应
3.16 文件上传下载
1、文件上传
步骤
-
表单的
method属性值必须是POST,由于GET方式提交是数据大小不能超过2KB,而POST是没有限制的<form action="/upload" method="POST"> -
需要使用到上传控件,该上传控件的type属性必须是
file<input type="file" name="headImg"/> -
由于上传的内容中有图片,图片是属于二进制类型的数据,不能进行字符编码,修改表单的编码方式,改成使用二进制的方式来编码
<form action="/upload" method="POST" enctype="multipart/form-data"> <!--表单 enctype 属性的默认值 application/x-www-form-urlencoded -->
注意
使用multipart/form-data编码后就不能使用req.getParamter()方法来获取参数了,该方法只能获取到URLEncoder编码的参数,multipart/form-data是表单的二进制编码,要用getInputStream() 方法来得到输入流,然后从输入流中读取传送的内容,再根据文件上传的格式进行分析,取出长传文件的内容和表单中其他字段的内容。
文件上传格式分析
-
CRLF表示回车换行。 -
boundary表示传送内容的分隔符,这是随机的,不同浏览器采用的分隔符形式也是不同的。
commons-fileupload 组件
Commons是 Apache 开放源代码组织中的一个 Java子项目,该项目涉及文件上传、命令行处理、数据库连接池、XML配置文件处理等。commons-fileupload就是用来处理基于表单的文件上传的组件。该组件还依赖Apache 的另一个项目commons-io。
在 commons-fileupload 组件中,主要用到下面一个接口两个类:
FileItemDiskFileItemFactorySevletFileUpload
SevletFileUpload 负责处理上传的文件数据,并将每部分的数据封装到一个 FileItem对象中。FileItem是一个接口,DiskFileItem类继承了此接口,上传的数据封装到了DiskFileItem。DiskFileItemFactory 用来创建 FileItem 对象的工厂类,在这个工厂类中可以配置内存缓冲区大小和存放临时文件的目录。
FileItem接口中定义的主要方法如下:
| 方法 | 描述 |
|---|---|
public byte[] get() | 以字节数组的形式返回文件数据项的内容 |
public String getContentType() | 返回客户端浏览器设置的文件数据项的MIME类型 |
public String getFieldName() | 返回文件数据项对应的表单中的字段的名字 |
public InputStream getInputStream() | 返回一个输入流,通过这个输入流来读取文件的内容 |
public String getName() | 返回在客户端文件系统中文件的原始文件名,这是由客户端的浏览器提供的 |
public OutputStream getOutputStream() | 返回一个输出流,利用这个输出流可以存储文件的内容 |
public long getSize() | 返回文件数据项的大小 |
public String getString() | 使用默认的字符编码,以字符串的形式返回文件数据项的内容 |
public String getString(String charset) | 使用指定的编码方式,以字符串的形式返回文件数据项的内容 |
public boolean isFormField() | 判断FileItem对象是否表示了一个简单的表单字段。 |
public void write(File file) | 将文件数据项的内容写到硬盘上。 |
案例
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>上传</title>
</head>
<body>
<div style="text-align:center;">
<form action="/upload" method="post" enctype="multipart/form-data">
账号<input type="text" name="username"><br/>
邮箱<input type="text" name="email"><br/>
头像<input type="file" name="headImage"><br/>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
UploadServlet.java
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// 检查当前请求是否符合文件上传的规定 menthd=post enctype=multipart/form-data
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (isMultipart) {
// 创建一个磁盘文件工厂,用来创建DiskFileItem
DiskFileItemFactory factory = new DiskFileItemFactory();
// 创建一个文件处理上传对象
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析上传的文件流,得到FileItem 对象的列表
List<FileItem> items = upload.parseRequest(req);
// 遍历
for (FileItem item : items) {
// 获取表单中的字段名称,即<input type="text" name="username">中的 username
String name = item.getFieldName();
//判断FileItem对象是否表示了一个简单的表单字段
if (item.isFormField()) {
// 获取表单字段填写的值,即用户输入的值
String value = item.getString("UTF-8");
System.out.println("name:" + name + " value:" + value);//name:username value:123
} else {
//在IE中获取到的是文件的绝对路径,
//而在W3C浏览器中获取到的是文件的简单名称,即文件上传的名称,即图片的名称 xxx.png
name = item.getName();
//获取表单中字段的名称,即headImage
String name1 = item.getFieldName();
//避免IE浏览器获得绝对路径,使用commons.io.FilenameUtils的getname()方法,获取文件的名称
String fileName = FilenameUtils.getName(name);
//P80520-150121.jpg----------headImage------------P80520-150121.jpg
System.out.println(name + "----" + name1+"--------"+fileName);
// 避免文件名称相同会发生文件覆盖的问题,使用UUID的值来替代文件的名称,getExtension方法获取文件的扩展名
fileName = UUID.randomUUID().toString() + "." + FilenameUtils.getExtension(item.getName());
//为方便能访问到上传的文件,这些文件应该放在项目中,在web根路径下建一个upload目录
String dir = req.getServletContext().getRealPath("upload");
item.write(new File(dir, fileName));
}
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
commons.io包里的工具类FilenameUtils里的常用方法:
String path= "F:\\FileUpload\\image\\1.png";
//获取文件的名称没有拓展名
System.out.println(FilenameUtils.getBaseName(path));//1
//获取文件的拓展名
System.out.println(FilenameUtils.getExtension(path));//png
//获取文件的名称
System.out.println(FilenameUtils.getName(path));//1.png
//获取文件的前缀
System.out.println(FilenameUtils.getPrefix(path));//F:\
问题:在IE中获取到的是文件的绝对路径,而在W3C浏览器中获取到的是文件的简单名称,如果使用了文件的绝对路径,在文件保存的时候就会有路径找不到的问题。
解决方案:FilenameUtils.getName()方法来获取文件的名称
String fileName = FilenameUtils.getName(item.getName());
问题:文件名称相同会发生文件覆盖的问题:
解决方案:使用UUID的值来替代文件的名称
fileName = UUID.randomUUID().toString()+ "." + FilenameUtils.getExtension(fileName);
问题:上传的到文件应该放在项目能访问的位置,为方便能访问到上传的文件,这些文件应该放在项目中
解决方案:在项目中专门使用一个文件夹来存上传的文件 ,在web根路径下建一个upload目录
String dir = req.getServletContext().getRealPath("upload");
item.write(new File(dir, fileName));
文件上传是有缓存大小的限制:数据的大小符合则存放在内存,不符合规则不会存放在内容中
文件上传过程中的临时目录:超过缓存大小的文件在上传是会存放在临时目录中, 该目录位于tomcat根/temp的文件夹中(临时目录是可以改的,但是不建议改)
该工厂的默认缓存大小是10KB,默认超过10KB就存放在临时目录
/**
* The default threshold above which uploads will be stored on disk.
*/
public static final int DEFAULT_SIZE_THRESHOLD = 10240;
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(repository) //设置临时目录
factory.setSizeThreshold(20*1024) //设置缓存大小
上传文件大小约束
-
情况1:单个文件超过指定的大小.
-
情况2:该次请求的全部数据超过指定的大小.
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(3*1024); //设置单个文件不超过3KB
upload.setSizeMax(5*1024); //设置请求内容大小不能超过5KB
2、文件下载
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>下载</title>
</head>
<body>
<div style="text-align:center;">
<h3>下载资源</h3>
<a href="/download?fileName=0f5881d7-c732-4b98-8f6c-9a22b4345d76.jpg">下载</a>
</div>
</body>
</html>
@WebServlet("/download")
public class DownLoadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、接受请求参数
String fileName = req.getParameter("fileName");
//解决中文乱码问题
fileName = new String(fileName.getBytes("ISO-8859-1"),"utf-8");
//2、找到资源的绝对路径
String path = getServletContext().getRealPath("/upload/"+fileName);
//设置文件下载使用的报头域
resp.setContentType("application/x-msdownload");
String str = "attachment;filename="+fileName;
resp.setHeader("Content-Disposition", str);
// 3、磁盘->内存->浏览器
Files.copy(Paths.get(path), resp.getOutputStream());
}
}
《Servlet JSP深入详解》 ——孙鑫

1万+

被折叠的 条评论
为什么被折叠?



