Servlet是sun公司提供的一门用于开发动态web资源的技术,可以实现和客户端的交互,接收客户端请求和给客户端返回响应。
Sun公司在其API中提供了一个servlet接口,用户若想开发一个动态web资源需要完成以下2个步骤:
Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。
HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
基础操作步骤:
编程工具上设置Tomcat配置。(有的是单独在项目中设置)
创建web项目。
以2020版本的idea为例:(先创建基础的java项目,再在项目右键,点击Add Frameworks Support 添加,选择web项目。)
注:不同的版本、编程软件也有不同的创建方式,有得可以直接创建web项目。
创建一个类,继承HttpServlet,重写service方法。
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用getParameter方法接受key为str的value值。
String str = req.getParameter("str");
System.out.println("前端发送了一个请求"+str);
//使用字符流打印返回浏览器一个响应。
resp.getWriter().print("hhhhh");//响应返回一个hhhh
//注:可以直接返回HTML语言(js+css+html)
//resp.getWriter().print("hhhh
");
}
}
注:在eclipse和myeclipse上,类能够直接继承HttpServlet,因为实现了自动导包。(idea需要手动导入servlet-api.jar包,才能正常extends HttpServlet类)
servlet-api.jar包在Tomcat的lib目录下。
注册Servlet类(让web服务器识别。)
在web.xml文件中设置创建的Servlet类信息
<servlet>
<servlet-name>MyServletservlet-name>
<servlet-class>com.dream.servlet.MyServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>MyServletservlet-name>
<url-pattern>/myservleturl-pattern>
servlet-mapping>
<body>
欢迎来到web项目
<br>
<a href="myservlet?str=hello">点击发送请求a>
<br>
<input type="button" value="点击发送请求" onclick="fun01()">
<script type="text/javascript">
function fun01(){
location="myservlet?str=hello"
}
script>
body>
在web.xml中设置进入项目默认访问的第一个文件。
这里设置为上面的welcome.html
<welcome-file-list>
<welcome-file>welcome.htmlwelcome-file>
welcome-file-list>
拓展:前端发送请求的三种方式:(前两种都只能以get方式提交,而第三种能够选择提交方式)
Tomcat在加载Web应用时,就会把相应的web.xml文件中的数据读入到内存中。因此当Tomcat在解析web请求的时候,需要参考web.xml文件时,实际上只需要从内存中读取相关数据就可以了,不需要再到文件系统中读取web.xml。
图示:
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
1,Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步。
2,装载并创建该Servlet的一个实例对象。
3,调用Servlet实例对象的init()方法。
4,创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5,WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
测试:
public class MyServlet01 extends HttpServlet {
public MyServlet01(){
System.out.println("MyServlet01调用构造方法,创建对象");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyServlet01接受到了来自前端的请求");
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("MyServlet01进行初始化");
}
@Override
public void destroy() {
System.out.println("MyServlet01销毁");
}
}
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
如果在元素中配置了一个元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的公共数据。
<servlet>
<servlet-name>MyServlet01servlet-name>
<servlet-class>com.dream.servlet.MyServlet01servlet-class>
<load-on-startup>1load-on-startup>
servlet>
Servlet被创建的两种情况:
1.前端发送请求,tomcat服务器发现该Servlet没有对象时就创建
2.在web.xml中配置 1,该Servlet就会在项目启动时由Tomcat创建对象。
Servlet被销毁:
Tomcat关闭时调用destroy()销毁的方法
注:
一般情况下Servlet创建对象只创建一次,符合单例模式(在整个项目中该Servlet的对象是唯一),init() 初始化方法 也只调用一次,而doGet() 或 doPost() 每请求一次就会调用一次。
在Servlet的配置文件中,可以使用一个或多个标签为servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
阅读ServletConfig API,并举例说明该对象的作用:
获得字符集编码
获得数据库连接信息实际的Servlet开发中,可以直接通过getServletConfig()的到ServletConfig对象。
<servlet>
<servlet-name>MyServlet01servlet-name>
<servlet-class>com.dream.servlet.MyServlet01servlet-class>
<init-param>
<param-name>codeparam-name>
<param-value>UTF-8param-value>
init-param>
servlet>
注:为键值对存在,放在servlet标签下,代表这个标签初始化的参数信息,在该servlet的init初始胡方法中能够获取到,使用示例:
@Override
public void init(ServletConfig config) throws ServletException {
String code = config.getInitParameter("code");
System.out.println("MyServlet01进行初始化,获得编码格式:"+code);
}
使用@WebServlet注解在Servlet类上,能够代替在web.xml配置文件中的一些相关设置,更加方便。
@WebServlet注解底层:
@Target({
ElementType.TYPE}) //作用于类
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Documented
public @interface WebServlet {
//指定Servlet 的 name 属性,等价于 没有显式指定,则该 Servlet 的取值即为类的全限定名。
String name() default "";
//该属性等价于 urlPatterns 属性。两个属性不能同时使用。标签。
String[] value() default {
};
String[] urlPatterns() default {
};
//指定 Servlet 的加载顺序,等价于 标签。默认为-1即不在服务器启动时创建。
int loadOnStartup() default -1; //
WebInitParam[] initParams() default {
};// 配置初始化参数 注解数组见下
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
//键值对,表示指定一组 Servlet 初始化参数,等价于标签。
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebInitParam {
String name();
String value();
String description() default "";
}
简单使用:
@WebServlet(
value = "/MyServlet",
loadOnStartup = 1,
initParams = {
@WebInitParam(name ="code",value = "UTF-8")
})
public class MyServlet extends HttpServlet {
public MyServlet(){
System.out.println("MyServlet调用构造方法,创建对象");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyServlet接受到了来自前端的请求");
}
@Override
public void init(ServletConfig config) throws ServletException {
String code = config.getInitParameter("code");
System.out.println("MyServlet进行初始化,获得编码格式:"+code);
}
@Override
public void destroy() {
System.out.println("MyServlet销毁");
}
}
运行结果:
MyServlet调用构造方法,创建对象
MyServlet进行初始化,获得编码格式:UTF-8
......
MyServlet接受到了来自前端的请求
......
MyServlet销毁
......
Disconnected from server
出现原因:多个客户端访问同一个Servlet中的资源时,有可能会出现线程安全问题
测试案例:
在servlet中增加线程占用的时间(sleep方法),让多个浏览器去访问这个servlet,其中的数据读取就会出现脏读。
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet{
//请求次数
int num;
public MyServlet() {
System.out.println("MyServlet被创建");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
num++;
try {
Thread.sleep(5000); //线程睡眠5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getOutputStream().println(num);
}
}
在相差五秒内,前后使用两个浏览器访问这一个servlet,得到的请求次数(num)应该为:
先访问的页面出现1,而后访问的应该出现2。
但实际上两个页面都会显示2,这是因为在多线程访问一个servlet时,数据同时被两个线程处理,导致有些线程得到的数据不再准确。(在真正的项目中虽然没有sleep方法影响,servlet处理请求不会故意睡眠,但是在各种因素的影响下,我们还是不能保证数据的可靠性。)
处理方法:
1.将Servlet实现SingleThreadModel(已过时),因为当线程阻塞,就会创建新的Servlet对象(一般不使用,因为在这种情况下,servlet的对象不再是唯一的。)
2.利用线程锁机制, synchronized或lock
示例:在需要的处理请求的代码块上锁
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet{
//请求次数
int num;
//创建lock锁对象。
private Lock lock = new ReentrantLock();
public MyServlet() {
System.out.println("MyServlet被创建");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
lock.lock();//上锁
num++;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getOutputStream().println(num);
lock.unlock();//解锁
}
}
在前端页面,想要跳转到其他的页面或者是Servlet,可以直接通过超链接、提交表单、location三种实现,但是单纯两个页面之间的的跳转,是没有经过后端任何数据处理和数据传递的,这样的实现在大多数情况下是没有意义的。所以在需要数据处理和页面之间的数据传递时,常常会经过Servlet来实现。
Servlet跳转到某个页面/Servlet的两种方式为:重定向和转发。
重定向是通过响应(response)来完成的,而转发是通过请求(request)完成。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在Servlet中使用 重定向方式 跳转到page.html
response.sendRedirect("page.html");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在Servlet中使用 转发方式 跳转到page.html
request.getRequestDispatcher("page.html").forward(request,response);
}
注:请求转发时,需要将request和response一同转发。
浅显的说:
重定向是Servlet告诉浏览器,自己无法完成你的请求(第一次请求),并且通过响应告诉浏览器你想要完成这个请求应该去找谁,之后浏览器根据响应回来的信息(重定向的内容)重新给新的servlet或页面发送请求(第二次请求)。
转发则是Servlet知道自己无法完成浏览器的请求(一次请求),但是,我可以去找其他Servlet或页面帮你完成,而这些则不需要浏览器再操心。
(浏览器分别找重定向和转发借钱;
转发说:我虽然没有钱,但是我可以找别人借了,再借给你。
重定向说:我也没有钱,你找马云借吧,然后浏览器又去找马云借去了。(借了两次))
重定向 | 转发 | |
---|---|---|
前端发送请求的次数 | 2次 | 1次 |
跳转到普通页面(项目web文件夹下的页面) | ok | ok |
跳转到外部页面的区别(如http://www.baidu.com) | ok | no |
跳转到受保护页面的区别(WEB-INF里的页面) | no | ok |
解释:
1.转发无法跳转到外部页面的原因:当前服务器不能直接访问外部服务器。
2.重定向无法跳转到受保护页面的原因:浏览器不能直接访问WEB-INF内的资源
出现原因:
前端后端编码不一致
浏览器默认使用UTF-8码表进行编码 ,Servlet使用ISO-8859-1码表进行编码
传输和接收方编码不一致导致乱码的产生
Request请求分为post和get,分别有不同的解决方案
在Servlet的doPost方法中给请求的参数设置编码格式。
示例:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//先给请求中的参数设置编码格式
request.setCharacterEncoding("UTF-8");
//再通过getParameter获取数据。
}
注:
在执行 setCharacterEncoding()之前,不能执行任何 getParameter()操作。
通过 setCharacterEncoding 设置的编码方式只对 POST 方式提交的表单有效,对 GET 方式无效。
解决方案一:将获取到的数据先采用ISO-8859-1的格式解码成字节数组,在通过UTF-8的编码格式重写编码成完整数据。
示例:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String parameter1 = request.getParameter("parameter1");
parameter1 = new String(parameter1.getBytes("ISO-8859-1"),"UTF-8");
String parameter2 = request.getParameter("parameter2");
parameter2 = new String(parameter2.getBytes("ISO-8859-1"),"UTF-8");
}
解决方案二:在Tomcat根目录/conf/server.xml中设置编码格式
示例:
<Connector URIEncoding="UTF-8" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000" redirectPort="8443"
/>
设置完成之后,在启动服务器的时候就会读取到URIEncoding的属性配置,然后再调用自己的一个setURIEncoding方法完成设置,形参的值为我们设置的UTF-8 。
底层setURIEncoding方法:
protected String URIEncoding = null;
public void setURIEncoding(String URIEncoding) {
this.URIEncoding = URIEncoding;
setProperty("URIEncoding", URIEncoding);
}
Tomcat8.x的服务器在接收GET请求时,即使参数中有中文,也不会出现乱码,作者在底层设计上的一些改动
Tomcat的连接器组件(Connector) ,Connector是Tomcat中的一个重要的组件,它负责监听Tomcat收到的请求信息,并将这些请求信息传递给Servlet规范中所定义的Request,然后将转换后的请求交给Engine组件
去处理,最后将Engine返回的Response返回给客户端 。源码中我们可以看到, URIEncoding的默认值为UTF-8,所以在Tomcat8.x中,即使GET请求包含了中文的数据,也不会出现乱码了
public class Connector extends LifecycleMBeanBase {
private Charset uriCharset = StandardCharsets.UTF_8;
//查询Tomcat根目录/conf/catalina.properties配置文件中的属性
public static final boolean RECYCLE_FACADES =
Boolean.parseBoolean(System.getProperty("org.apache.catalina.connector.RECYCLE_FACADES", "false"));
public Connector() {
this(null);
}
public Connector(String protocol) {
setProtocol(protocol);
ProtocolHandler p = null;
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
}
}
单独设置响应内容的编码格式
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应内容的编码格式
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("哈喽!");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyServlet接受到了来自前端的请求");
//使用重定向的方式 跳转到 详情页面.html
resp.sendRedirect(URLEncoder.encode("详情页面.html","UTF-8"));
//使用转发的方式 跳转到 详情页面.html
//req.getRequestDispatcher("详情页面.html").forward(req,resp);
}
注:
1.通过转发的方式,Servlet可以识别中文,不需要其他改变,而重定向则需要如上述配置。
e) throws ServletException, IOException {
//设置响应内容的编码格式
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("哈喽!");
}
### 2.7.3 跳转到中文页面路径乱码
```java
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyServlet接受到了来自前端的请求");
//使用重定向的方式 跳转到 详情页面.html
resp.sendRedirect(URLEncoder.encode("详情页面.html","UTF-8"));
//使用转发的方式 跳转到 详情页面.html
//req.getRequestDispatcher("详情页面.html").forward(req,resp);
}
注:
1.通过转发的方式,Servlet可以识别中文,不需要其他改变,而重定向则需要如上述配置。
2.不建议使用中文路径页面。