从零开始学 serlvet
一、hello servlet
写一个servlet,首先从tomcat中导包 jsp-api.jar + servlet-api.jar,写一个hello Servlet!
方法1.写一个java类继承genericServlet。并实现service方法。
方法2.写一个java类继承HttpSerlvet,并重写doPost() doGet()方法。
二、servlet的生命周期
任何一个servlet都包含init()、service()、destroy()方法。init在首次访问该servlet时进行初始化,而且只初始化一次。每次访问servlet都会调用service方法。在卸载servlet或结束项目时,调用destroy方法。如果想改变加载的优先级,需要更改
public class Servlet2 extends GenericServlet{
@Override
public void init() throws ServletException {
System.out.println("init");
}
@Override
public void destroy() {
System.out.println("destroy");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello generic Servlet!");
servletResponse.getWriter().print("Hello Servlet");
}
}
index.jsp
<%@ page contentType="text/html;charset=utf-8" %>hello
web.xml
<?xml version="1.0" encoding="UTF-8"?>a com.cskaoyan.Servlet2 1 a /helloServlet
第一次访问http://localhost:8080/helloServlet,出现init。这说明是在访问Servlet时再执行init()。且只执行一次。
以后多次访问只会执行Service()方法。
结束时会执行destroy()方法。
结果:
load-on-startup
但是,如果在web.xml的
结果:
也就是说,load-on-start 作用是:当数值为正整数,在项目启动时就初始化,而不是在访问到该类再初始化。数值越小优先级越高。
load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。 它的值必须是一个整数,表示servlet被加载的先后顺序。 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
关于url-pattern的一些细节问题
1、在web.xml中的写法
a com.cskaoyan.Servlet2 1 a /helloServlet
等价于
在标记的类上添加注解:
@WebServlet(value="/servlet1")或 @WebServlet(urlPatterns = {"/servlet1",“/sjdfk”})
2、关于urlPattern的映射问题:
一个servlet可以设置多个url-pattern吗?可以的。
@WebServlet("/TestUrlPattern")
public class TestUrlPattern222 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("/TestUrlPattern222");
servletResponse.getWriter().write("/TestUrlPattern222");
}
}
@WebServlet(urlPatterns = {"/TestUrlPattern","/TestUrlPattern2"})
public class TestUrlPattern extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("/TestUrlPattern");
servletResponse.getWriter().write("/TestUrlPattern");
}
}
运行报错:Caused by: java.lang.IllegalArgumentException: 名为 [TestUrlPattern]和 [TestUrlPattern222] 的servlet不能映射为一个url模式(url-pattern) [/TestUrlPattern]
3.关于UrlPattern中 / * 与 / 优先级
servlet1:
@WebServlet("/abc/*")
public class TestUrlPriority extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("/abc/*");
servletResponse.getWriter().write("/abc/*");
}
}
servlet2:
@WebServlet("/*")
public class TestUrlPriority2 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("/*");
servletResponse.getWriter().write("/*");
}
}
servlet3:
@WebServlet("/abc")
public class TestUrlPriority3 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("/abc");
servletResponse.getWriter().write("/abc");
}
}
servlet4:
@WebServlet("*.do")
public class TestUrlPriority4 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("*.do");
servletResponse.getWriter().write("*.do");
}
}
测试结果:
/**
* 访问:/abc/a.html 结果: /abc/*
* /abc /abc
* a.do /*
* /xxx/yyy/a.do /*
*/
结论:URL精准匹配优先级最高,/* 的优先级高于/ 高于 *.xxx
因此实际开发中存在以下问题:
4. @WebServleturl(Pattern= ”/“)存在的问题
问题:当@WebServleturl(Pattern= ”/“) 访问不到除了jsp以外所有的静态资源,当@WebServleturl(Pattern= ”/* “)访问不到所有的静态资源
原因:tomcat 中对静态资源文件有特定处理
在tomcat目录下web.xml文件,有以下字段:
default / jsp *.jsp *.jspx
如果自己写了一个Servlet的,而它的@WebServleturl(Pattern= “/*” ),那么 /* 的优先级高于 *.xxx ,则所有请求被拦截(包括静态资源请求),需要自己手动处理所有请求。如果没有重写jsp和其他静态资源的处理方式,就会导致访问不到所有的静态资源。
同理:如果@WebServleturl(Pattern= “/”),那么 / 优先级低于 *.xxx, 则除了jsp页面,其他静态资源都被拦截。自己没有重写,则访问不到(例如html)
测试代码如下:(app1为项目名)
Servlet1:
@WebServlet("/*")
//@WebServlet("/aabbcc")
public class SpecialServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.getWriter().print("/*");
}
}
Servlet2:
@WebServlet("/")
public class SpecialServlet2 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.getWriter().print("/");
}
}
测试结果:
访问:http://localhost:8080/app1/
结果: /*
访问:http://localhost:8080/app1/1.html
结果:/*
如果把servlet1的注解改为 @WebServlet("/aabbcc")
访问:http://localhost:8080/app1/
结果:正常显示jsp页面
访问:http://localhost:8080/app1/1.html
结果:/
结论:/*优先级高于*.xxx,*.jsp与jsp页面匹配度更高(高于/)
Servlet运行的流程图
servlet的 servletContext 与 ServletConfig
servletContext获取全局参数,对应xml中的
servletConfig 获取初始化参数,对应xml中的
servletContext域(共享)
servletContext域是指在当前应用下,所有servlet共用一块内存空间,servletContext数据共享。
通俗理解: 一个应用里面只有一个context对象,即使有多个servlet,只能拿到同一个context对象。参考jsp的四个域 page context session application
@WebServlet("/servlet1")
public class Servlet1 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
this.getServletContext().setAttribute("name","servlet1");
}
}
@WebServlet(value = "/servlet2")
public class Servlet2 extends GenericServlet{
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//servletResponse.getWriter().print("Hello Servlet");
String name = (String) getServletContext().getAttribute("name");
servletResponse.getWriter().write(name);
System.out.println(name);
}
}
然后先访问:http://localhost:8080/servlet1 浏览器结果:无
再访问:http://localhost:8080/servlet2 浏览器结果:出现servlet1
这说明servletcontext是共用的。
绝对路径与相对路径
在servlet中很少new File(),因为new File的默认路径为tomcat的bin目录。而我们关心的是部署目录。而使用getRealPath()和getServletContext()获取部署目录的绝对和相对路径。从而可以获得WEB-INF目录下的文件。而WEB-INF同级别文件可以通过浏览器 Http:localhost:8080/项目名/文件名 ,直接访问
@WebServlet("/servlet3")
public class servlet3 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
File file = new File("1.html");
System.out.println(file.exists());
System.out.println(file.getAbsolutePath());
String realPath = getServletContext().getRealPath("WEB-INF\\1.html");
System.out.println(realPath);
String contextPath = servletRequest.getServletContext().getContextPath();
System.out.println(contextPath);
}
}
访问:http://localhost:8080/app1/servlet3
结果:
1.全路径:http://localhost:8080/app1/page/2.html
好不好呢?全路径如果使用这种硬编码格式来书写,肯定是不好的
企业开发:开发环境(个人主机)、测试环境、生产环境
如果要写的话,也可以,但是必须将这些可变的部分以配置文件的形式配置起来,从配置文件中读取
2.相对路径:img src ='1.jpeg'
http://localhost/app1/2.html
http://localhost/app1/TestRequest2
(过分依赖于当前的页面路径)
3./开头的路径:/项目应用名/资源路径
比如:/app1/TestRequest2(推荐的写法)
练习:用以上三种不同的写法,填写表单路径。浏览器访问页面为与WEB-INF同级page目录下的2.html
public class TestRequest2:
@WebServlet("/TestRequest2")
//webservlet值拼接的是...的内容。----> http:localhost:8080/app1/" ... "
public class TestRequest2 extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse servletResponse) throws ServletException, IOException {// TO DO}
}
page文件夹下的2.html
222
学习 Request
练习:写一个 Servlet 和 form表单,接收传入参数并打印在控制台。
提示:使用ServletRequest 的 getParameterNames() 、 getParameter()、getParameterValues() 方法
实现类:
@WebServlet("/TestRequest")
public class TestRequest2 extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse servletResponse) throws ServletException, IOException {
//System.out.println(req.getParameter("username"));
//System.out.println(req.getParameter("password"));
//String hobbies = req.getParameter("hobby");
//参数一个一个获取太繁琐了,一个for搞定它
System.out.println("_________________________");
Enumeration parameterNames = req.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
//String parameter = req.getParameter(parameterName);
String[] parameters = req.getParameterValues(parameterName);
// Don't print submit
if(parameterName.equals("submit"))
continue;
// The parameters maybe only one element or an Array. Use if statement to judge
if(parameters.length==1){
System.out.println(parameterName+":"+parameters[0]);
}
else {
System.out.println(parameterName+":"+Arrays.toString(parameters));
}
}
}
}
form表单:1.html
111
访问:http://localhost:8080/app1/1.html
结果:
username:123@qq.com
password:111
gender:male
hobby:[java, python, c++]
三、JavaBean
什么是javaBean
JavaBean: java里面的对象类。类似于Student类。成员变量是private,提供相应的get和set方法;一定要有无参构造函数.
练习:通过反射,将 form 表单中的参数封装到 user 类中。
。。。。。
转发与包含
servlet处理一个逻辑之后,需要跳转至一个页面,那么这个时候可以怎么做呢?
转发:源组件执行一部分逻辑之后,接下来将请求交给目标组件来继续处理,后面主动权交给目标组件
包含:源组件执行一部分逻辑之后,接下来将目标组件包含到自身之中来,一并做出响应,并没有移交主动权
代码层面的体现:
转发:(源组件)留头不留体(响应头、响应体)
下文的TestForward类中关于req.getRequestDispatcher("/跳转目标");注意这个分发器的参数,可以是相对路径“跳转目标”,也可以"/跳转目标"。但绝对路径不行,因为它会帮你自动添加”/项目名“。全路径的报错结果如下:
------------------------------
forward 与 include 比较测试代码:
TestForward:
@WebServlet("/TestForward")
public class TestForward extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//响应头
resp.setHeader("Content-Type","text/html;charset=utf-8");
//响应体
resp.getWriter().print("TestForward");
RequestDispatcher dispatcher = req.getRequestDispatcher("/TestForward2");
dispatcher.forward(req,resp);//测试forward
// dispatcher.include(req,resp);//测试include
resp.getWriter().print("TestForward");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
TestForward2:
@WebServlet("/TestForward2")
public class TestForward2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("TestForward2");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
首先测试forward
浏览器访问:http://localhost:8080/app1/TestForward
浏览器结果:
结论:对于forward:响应头setHeader成功,而响应体 resp.getWriter().print("TestForward");失败
而测试include:发现响应头与响应体都成功(结果就不再演示)。
测试2:登录后的转发页面
Login类:
@WebServlet("/login")
public class Login extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username.equals("admin")&&password.equals("admin")){
req.getRequestDispatcher("/success.html").forward(req,resp);
}else{
req.getRequestDispatcher("/fail.html").forward(req,resp);
}
}
}
login.html
login
success.html:
success
success
fail.html
fail
fail
浏览器访问:http://localhost:8080/app1/login.html,即可查看结果。