深入浅出servlet


深入浅出servlet

1. 模拟Servlet

  • 定义Servlet规范
/**
 * 定义Servlet接口/规范
 */
public interface Servlet {
    //一个专门提供服务的方法
    void service();
}
  • webapp中实现Servlet
public class BankServlet implements Servlet{
    @Override
    public void service() {
        System.out.println("BankServlet");
    }
}
  • 编写tomcat服务器容器应用
public static void main(String[] args) throws Exception {
    System.out.println("Tomcat server starting,beging to receive");
    //I/O模拟
    //用户访问服务器是通过浏览器上的请求路径来进行的
    //localhost:8080/userList => userListServlet(使用)
    Scanner in = new Scanner(System.in);
    String path = "";
    if(in.hasNextLine()){//用户输入请求路径
        path = in.nextLine();
    }
    //Tomcat服务器通过用户的请求路径去找对应的Servlet
    //请求路径和Servlet的关系是由开发者指定的
    //对于tomcat服务器来说需要解析配置文件
    FileReader fileReader = new FileReader("/web.properties");
    Properties properties = new Properties();
    properties.load(fileReader);

    //通过key获取value
    String className = (String)properties.get(path);
    Class<?> clazz = Class.forName(className);

    //获取对应的servlet对象
    Servlet myServlet = (Servlet)clazz.newInstance();
    myServlet.service();
}

从上面可以看出,关键的一环是定义请求URL和具体的servlet之间对应的关系,这个关系需要程序员进行配置

遵循Servlet规范的webapp,这个webapp就可以放在不同的web服务器中运行

严格意义上来说Servlet其实并不是简单的接口,其中规定了

  • 规定的目录结构
  • 规定的配置文件
  • 配置文件的路径
  • java程序的存放路径
  • 规范了接口
  • 规范了类

Tomcat服务器和web开发者要遵循其规范。

2. Servlet初步开发

[1]webapps下新建一个crm(项目目录),我们称这个目录为项目的根目录

[2]在根下新建一个一个目录:WEB-INF

[3]WEB-INF下新建一个classes 用来存放编译后的字节码文件

[4]新建lib,用于存放外部使用的jar包

[5]新建web.xml,该xml文件描述了请求路径和Servlet类之间的关系

[6]编写java程序,实现Servlet。

[7]将jar包加入到电脑的环境路径中,配置环境变量CLASSPATH

CLASSPATH=.;所导包的路径

[8]注册Servlet类:编写配置文件信息,让请求路径和Servlet类名关联在一起

<!--servlet描述信息-->
<!--任何一个servlet都对应一个servlet-mapping-->
<servlet>
    <servlet-name>aaa</servlet-name>
    <!--全限定类名-->
    <servlet-class>com.example.loginServlet</servlet-class>
</servlet>
<!--servlet映射信息-->
<servlet-mapping>
    <servlet-name>aaa</servlet-name>
    <!--这里需要一个路径-->
    <!--这个路径唯一的要求是以/开始-->
    <!--当前这个路径可以随便写,是请求路径 -->
    <url-patterb>/crm/login</url-patterb>
</servlet-mapping>

简单来说,机制是这样的,通过mapping中的urlpattern定位到servletname,然后通过servletname找到这个类,然后把这个类的字节码拿到,反射之后执行方法

[9]启动tomcat服务器

[10]访问测试

  • tomcat服务器的伪代码
//Tomcat服务器伪代码
Class<?> clazz = Class.forName("com.scau.cmi.LoginServlet");
Object newInstance = clazz.newInstance();

//向下转型
Servlet servlet = (Servlet) newInstance;
//创建ServletConfig对象
//Tomcat服务器负责将ServletConfig对象实例化出来
ServletConfig servletConfig =(ServletConfig) new Object();
//调用init方法
servlet.init(servletConfig);

3. Servlet生命周期

对象生命周期:Servlet什么时候被创建,什么时候被销毁,创建了几个?从创建到销毁的整个过程是怎样的?

  • 对象的构造

Servlet对象的创建,对象上方法的调用,对象的最终销毁,编写web应用的人员是无权干预的,而是由Tomcat服务器来进行的。也就是Web容器来管理Servlet对象的创建与销毁

注意:如果是我们自己new的servlet对象,那么这个对象不受web容器控制的,其控制机制是类似于:由tomcat创建的servlet对象会加入到一个列表,在tomcat启动服务和销毁服务的时候,将一一对该列表内的对象进行操作,而我们自己new出来的对象没有加入到列表中,是不受控制的

默认情况下,Tomcat服务器在启动的时候,Servlet对象是不会先实例化创建的,而应该是用户进行请求后再进行Servlet的创建与使用,是一种懒汉式的设计,当然了也可以通过配置servlet进行服务器创建时即创建servlet

<load-on-startup>1</load-on-startup>
<!--越小的整数优先级越高-->
  • init阶段

当Servlet对象被创建出来之后,tomcat马上调用了Servlet的init方法

  • 服务接收阶段

用户多次发送请求的时候,可以观察到Servlet并没有新建,而是使用之前已经创建好的Servlet对象,因此它的单实例的,但是Servlet并不符合单例模式,称之假单例。Tomcat只创建了一个,因此体现出来是单例的,但是其构造方法并不是私有的,依然可以new出来,因此不符合。

init方法只在第一次用户发送请求的时候执行。

只要用户发送一次请求,service方法必然会被调用。

  • destory阶段

关闭服务器的时候,服务器会调用destory(),注意这个方法是即将销毁,servlet的对象还存在,还没有被GC,因为实例方法必须依赖于实例对象才能被调用。

  • 被GC

当我们Servlet编写一个有参数的构造方法,如果没有手动编写无参数构造方法则会导致Servlet无法实例化

  • init方法存在的意义

Servlect的无参构造方法是在对象第一次创建的时候执行,并且只执行一次,init方法也是在对象第一次创建的时候执行,并且只执行一次,那么这个无参构造方法可以代替掉init方法吗?

不建议手动编写构造方法,这个操作容易让无参构造方法消失,这个操作可能导致serlvet对象无法实例化。

因此规范中提供了init()为servlet的初始化提供了入口

4. 适配器模式改造Servlet

例子:手机直接用220V电压来充电,手机会受不住,解决办法是找一个能够适配手机充电功率的充电器,以这个充电器来适配手机

在Servlet的例子中,对于那些长期不用的方法,或者不常用的方法,在适配器中将其实现,实现其实为假实现,将常用方法设置为abstaract,然后将类设置为abstract,然后针对某一个类群,这个类群只要去实现这个抽象类,就只需要实现这一个抽象方法,就可以做到只实现一些比较重要方法的效果

  • 初步改造
/**
 * 编写标准通用的Servlet
 * 以后所有的Servlet都不要直接实现Servlet
 * 而是先继承该接口
 * 显然,这就是一个适配器
 */
public abstract class GenericServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 子类必须实现这个方法
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException ;


    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}
  • 如何改造使得子类程序更容易编写
    • 在上述的GenericServlet之后,init方法还会执行吗?
      • 会,这是因为子类中没有实现的方法会委托给父类进行实现
    • init()方法最终还是会由Tomcat服务器进行调用
    • init()方法中的ServletConfig对象是谁创建的?是谁传过来的?
      • Tomcat会先将ServletConfig对象创建出来,然后调用init()方法,将ServletConfig对象传入init()
public static void main(String[] args) throws Exception {
    //Tomcat中的伪代码
    //创建LoginServlet,反射机制调用无参数构造方法
    Class<?> aClass = Class.forName("adapterDemo.UserServlet");
    Servlet UserServlet = (Servlet)aClass.newInstance();
    //创建完成
    //构造方法完成后
    //调用init方法
    //1.创建config对象(由tomcat负责
    UserServlet.init(new ServletConfig() {
        @Override
        public String getServletName() {//注入名称
            return null;
        }

        @Override
        public ServletContext getServletContext() { //这个不知道是啥
            return null;
        }

        @Override
        public String getInitParameter(String s) {//初始化参数,比如加载优先级
            return null;
        }

        @Override
        public Enumeration<String> getInitParameterNames() {//初始化参数列表
            return null;
        }
    });
    //调用service()

}
  • 对config的改造
//提升config的作用域
private ServletConfig config;
/**
 * init方法中的ServletConfig对象是tomcat创建好的
 * 这个ServletConfig对象目前在init方法的参数上,属于局部变量
 * 那么ServletConfig对象肯定以后需要在service方法中使用,如何才能保证ServletConfig对象在service中调用呢
 *
 * @param servletConfig
 * @throws ServletException
 */ 
@Override
public void init(ServletConfig servletConfig) throws ServletException {
    //注入
    this.config = servletConfig;
}

@Override
public ServletConfig getServletConfig() {
    return config;
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
    //注入
    this.config = servletConfig;
    this.init();
}
//模板设计模式
//我的init,子类如果有需求重写init,只需要修改这个init即可,这样可以保证要做的事情都做完
public void init(){

}

5. ServlerConfig详解

ServletConfig是什么?

servlet.ServletConfig是Servlet规范中的一员,被定义为规范的接口。

谁实现这个接口?

org.apache.catalina.core.StandardWrapperFacade实现了这个接口

也就是说是Tomcat实现了ServletConfig接口

如果把Tomcat换成了jetty服务器则由于实现子类改变了,则最终输出的结果也要改变。但是他们都实现了Servlet规范

一个Servlet对象中关联一个ServletConfig,是一对一的关系

ServlerConfig对象是Tomcat服务器创建的,在创建了Servler的时候创建了ServletConfig

ServletConfig对象中包装的信息是web.xml文件中的<servlet></servlet>标签的配置信息。

同时可以通过添加<init-param>来为config中添加配置信息

<init-parm>
    <param-name></param-name>
    <param-value></param-value>
</init-parm>
   String getInitParameter(String var1);//是一个键值对的形式,传入key输出value
   Enumeration<String> getInitParameterNames();//获取所有的初始化参数名,以集合的形式产生
   public ServletContext getServletContext() {}
public String getServletName() {}

6.ServletContext接口详解

ServletContext是什么?

ServletContext是接口,是Servlet规范中的一员

ServletContext是谁实现的?

Tomcat(web)服务器实现了ServletContext接口。

ServletConext对象是谁创建的,在什么时候创建的?

在web服务器启动的时候创建的,是web服务器创建的,对于一个webapp来说,servletContext对象只有一个,servletContext对象再服务器关闭的时候销毁。

如何理解context?

context在英文中翻译为上下文、环境,也就是Servlet对象的环境对象,也被称为应用域,上下文对象,ServletContext对象对应的其实就是整个web.xml文件。用来描述当前服务器的环境以及Servlet的存活环境。

放在ServletContext对象中的数据,所有的Servlet对象一定是共享的。

Tomcat是一个容器, 一个容器可以放多个webapp,一个webapp对应多个Servlet

如果所有的用户共享一份数据,并且这个数据很少地被修改,并且数据量较少,可以将其放到ServletContext 这个应用域中。

因为大数据量会影响服务器的性能。占用大量堆内存而且只有在服务器停止时才会被GC。

public interface ServletContext {
    Enumeration<String> getInitParameterNames();//获取所有的初始化参数的name
    String getInitParameter(String var1);		//通过键值来获取初始化的参数	
}

在web.xml可以进行对于上下文的配置

<context-param>
    <param-name>pageSize</param-name>
    <param-value>10</param-value>
</context-param>
<context-param>
    <param-name>my-name</param-name>
    <param-value>Lumxi</param-value>
</context-param>

这个位置上的信息对于所有的Servlet都是共享的

String getContextPath();
//获取当前应用的根路径,因为在java源代码中有一些地方可能需要应用的根路径,这个方法可以动态获取应用的根路径
//在java源代码中,不要将应用的根路径写死。
//例如我当前的webapp的项目路径是/servlte04
String getRealPath(String var1);
//动态获取文件的绝对路径,起点是从web文件夹下开始的,也就是起始路径是web
//加不加"/"的问题:一般来说,"/"代表的是根路径, 默认是从web目录下开始寻找

日志系统

void log(String var1);
void log(String var1, Throwable var2);
//日志会将记录到catalina_home/logs目录下
//catalina服务器java程序运行的控制信息
//localhost.xxx ServletContext记录的日志信息
//localhost_access_log.xxx 服务访问日志

设置环境共享属性

void   setAttribute(String name,Object value);
void   removeAttribute(String name);
Object getAttribute(String name);

在以后进行Servlet类的时候实际上是不会直接去继承GenericServlet类的,因为B/S的系统架构是基于Http超文本传输协议的,在 Servlet规范中,直接提供了一个类叫做HttpServlet,这个类是专门为Http协议准备的 Servlet类。

interface Servlet{}
abstract class GenericServlet implements Servlet{}
abstract class HttpServlet extends GenericServlet{}

其中抽象方法为service()方法,点开源码可以发现:

7. 详解Http协议

复习目前为止学过的所有缓存机制

  • 堆内存中的字符串常量池

abc现在字符串常量池中查找,如果有,则直接生成引用,如果没有则先再常量池中创建。

  • 堆内存中的整数型常量池

[-128,127]一共256Integer类型的引用,在类内部享元, 没有超过这个范围的话equals同一个对象,否则的话就会新建对象

  • 连接池(Connection)

连接池实际上是Java连接数据库的连接对象Connection,其底层原理应该是socket,我们知道生成socket一般是成对出现的,每次进行Socket的配对和监听都是要消耗资源的,但是Java程序一般来说,当运行起来了,客户机和服务机的端口以及Ip就不会再改变了,这时候就不应该再消耗额外的精力去进行通信线路的打通,而是复用之前就建立好的连接,因此就将建立好通信的描述对象Socket封装为Connection,放到集合等数据结构中,等到需要使用了之后,直接get()出来就好了。

  • 线程池

实际上是在Tomcat服务器启动的时候,就会创建多个工作线程,然后将线程对象放到集合当中,称为线程池,用户发送过来请求,封装成task,task交给Thread进行run(),如果一个task就对应一个Thread,那将产生大量的Thread的生成与销毁管理问题,上下文调度开销也会巨大,因此限定线程数量,实现线程的复用是合理的。

  • redis
  • ServletContext应用域中存储数据
  • get请求从服务端获取的缓存的

HTTP(发送消息的模板被提前制定好):W3C制定的一种超文本传输协议,这种协议游走在B和S之间,BS发数据要遵循HTTP协议,这样B和S才能解耦合。

超文本:简单来说就是不是普通文本,比如流媒体,不但可以传送普通字符串,还可以传递声音、视频、图片等

请求协议:浏览器向web服务器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准规定了发送的数据的具体格式。

HTTP的请求协议包括四部分:

  • 请求行
    • 请求方式:Get还是Post还是Put、Delete等七种方式
    • URI: 统一资源标识符,代表网络某个资源的名字。但是通过URI是无法定位资源的。
      • URL和URI什么关系,有什么区别?
      • URL是统一资源定位符,同时,通过URL是可以定位到该资源的,常见的URL的形式如http://localhost:8080/hello/index.html,而/hello/index.html就是URI
      • URL包含了URI
    • HTTP协议版本号
  • 请求头
    • 请求的主机
    • 主机的端口
    • 浏览器信息
    • 平台信息
  • 空白行:分割请求头和请求体
  • 请求体
    • 向服务器发送的具体数据。

请求协议的报文Get请求

数据在url

请求协议的报文Post请求

fromdata:username=lisi&userpwd=123(数据在请求体中)

响应协议:web服务器向浏览器发送数据的时候也需要遵循一套标准,也规定了数据的具体格式。

HTTP的响应协议包括四部分:

  • 状态行
  • 响应头
  • 空白行:分割响应头和响应体
  • 响应体

响应协议的报文

状态行由三部分组成:

  • 协议的版本号

  • 状态码(Http中规定的响应状态号不同的响应结果对应不同的号码)

    • 200表示响应成功,正常结束
    • 400表示访问的路径不存在,通常是因为要么是你路径写错了要么是路径写对了,但是服务器中对应的资源没有启动。
    • 405表示的是前端发送请求的方式和后端接收的请求的处理的方式不同
    • 500一般是服务端产生了异常。
  • 状态的描述信息

    • 例如ok:正常执行
    • not found:资源找不到

响应头:

  • 响应的内容类型
  • 响应的内容长度
  • 响应的时间

空白行:分割作用

响应体是响应的正文,这些内容是一个长的字符串,被浏览器以指定的规则解析渲染。

8. Get和Post请求的区别

  • 在浏览器上直接输入URL,敲回车,属于get请求
  • 在浏览器上直接点击超链接,属于get请求

当发送get请求的时候,请求参数会挂在URI的后面,并且在URI后面加上一个?各个参数之间以&连接,也就是说get请求是在请求行中提交数据的

get请求只能够发送普通的字符串,并且字符串的长度有限制,由于其会在请求行中出现,因此是有限制的

get比较适合从服务器端获取数据。

get请求是绝对安全的,因为get请求只是为了从服务器上获取数据,不会对服务器造成威胁

get请求是支持缓存的,因此在平时的浏览体验中,如果第一次访问网站的话,那么就会比较慢,在第二次访问的时候就能比较快了。

这个缓存是放在浏览器中的,一个get请求的路径对应于一个资源,任何一个get请求的最终的响应结果都会被浏览器缓存出来。当发送一个get请求的时候,它做的第一件事就是从浏览器缓存中找,找不到才会去服务端获取

如果不希望get走缓存怎么办? 只需要每一次请求的路径不同即可。一般的手段是通过添加时间戳在URL中即可。但是我们的后端是不接收这个时间戳的,这样就没有影响了。

当发送post请求的时候, 参数是封装在请求体之中的,在地址栏上是看不到的。

post请求能够发送任何数据类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片

post请求可以发送大数据量,理论上是没有长度限制的

post请求比较适合向服务器段传送数据

post请求是危险的,post请求是向服务器提交数据,如果这个数据通过后门的方式进入到服务器当中,服务器是危险的。post是为了提交数据,所以一般情况下拦截请求的时候,大部分时候会选择拦截监听post请求。

post请求是不支持缓存的。因为这个缓存没有意义。

注意,关于安全性的问题是建立在使用的选择正确的情况下而定的,其关键就是获取数据和提交数据这两个界限。

如果你是为了向服务器提交数据,那么你就用post请求,如果你是为了向服务器获取数据,那么你就用get请求

请求的数据格式均是name1=value1&name2=value2

s.split("&");
s.split("=");
 //通过字符串的拆分来获取name和value

9. HttpServlet源码分析

HttpServlet中使用了模板设计模式,模板类通常是一个抽象类,模板类当中的模板方法定义核心算法,这个方法通常是final的,但也可以不是final的,模板中的抽象方法就是不确定的方法,交给子类来实现,其核心特点是定义了算法的核心骨架,而其内部的部分细节实现可以延迟到子类。

HttpServlet是专门为Http协议准备的,比GenericServlet更加适合Http协议下的开发。

HttpServletRequest封装了什么信息?

封装了请求协议的全部内容。它会将请求协议的全部数据内容解析出来,然后将这些数据全部封装到request对象中。只要面向HttpServletRequest,就可以获取请求协议的全部内容。

Response对象同理。

Servlet的生命周期

用户的第一次请求:Tomcat服务器通过web.xml中定义的映射关系,利用反射机制(无参构造方法)创建Servlet对象,然后调用init()方法完成初始化,调用service()方法处理请求。

第二次请求:调用service()

服务器关闭:tomcat服务器调用Servlet对象的destroy()方法,做销毁前的工作,然后被销毁。

//通过此无参构造方法创建对象,第一步,通过反射机制new出来一个instance()
public TestServlet(){}
 
//如果没有提供init()方法,那么必然执行父类的init()初始化方法
//通过源码追踪可以发现其init()方法是交给GenericServlet.init()实现的
//Tomcat(web服务器)是调用GenericServlet.init(ServletConfig)的
@Override
public void init(){
    System.out.println("here!");
}
 
//如果没有提供service()方法,那么必然会执行父类HttpServlet类的service()方法
//因为我们追踪源码发现其实现了service()方法
/**
 *     public void service(ServletRequest req, ServletResponse res)
 *         throws ServletException, IOException {
 *         if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
 *             //在上级方法进行适配,将普通的request、reponse对象转化为Http的对象,然后调用相关方法
 *             //进行关键对象的向下转型
 *             HttpServletRequest 	request = (HttpServletRequest)req;
 *             HttpServletResponse response = (HttpServletResponse)res;
 *             this.service(request, respo nse);
 *         } else {
 *             throw new ServletException("non-HTTP request or response");
 *         }
 *     }
 */
 
//覆盖步骤,doGet,doPost,如果没有覆盖service()的方法话就能够完成方法的分发,
//当然了自己写也是可以的
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    super.doGet(req, resp);
}
 
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    super.doPost(req, resp);
}
  • HttpServlet模板方法:
protected void service(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
    	//模板方法设计模式
    	//这个就是模板方法,这个是核心算法骨架
    	//其具体的实现步骤如doGet(),doPost()...等
    	//但是要注意service()也可以被强行重载,没有被final
    	//强行重载不当会引发异常。
        String method = req.getMethod();//获取请求的方式,做请求分发
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }
  • HttpServlet适配Adapter方法
public void service(ServletRequest req, ServletResponse res) 
    throws ServletException, IOException {
    if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
        //在上级方法进行适配,将普通的request、reponse对象转化为Http的对象,然后调用相关方法
        HttpServletRequest 	request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        this.service(request, response);
    } else {
        throw new ServletException("non-HTTP request or response");
    }
}

交给我们重写的方法是doGetdoPost等步骤方法

  protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
      String protocol = req.getProtocol();
      String msg = lStrings.getString("http.method_get_not_supported");
      resp.sendError(this.getMethodNotSupportedCode(protocol), msg);
  }//这个方法没有被重写的话,抛出405错误,如果service()没有被重写的话,
//就会执行这个逻辑,因为根据请求行,这个请求会被分发到对应的步骤方法中

10.关于web站点的欢迎页面

当为webapp设置了欢迎页面的时候,当你访问这个webapp的时候,或者访问这个web站点的时候,没有指定任何资源路径,这个时候会默认访问欢迎页面,也就是当访问形如localhost:8080/setvlet06/这样的路径时候,由于没有指定任何的静态资源路径,它就会默认访问你的欢迎页面

可以在web.xml中设置你的欢迎页面
一个webapp是可以设置多个欢迎页面的,越靠上的优先级越高,当一个404了就会开始自动扫描下面的页面

<welcome-file-list>
    <!--要注意路径:代表的当前webapp的根路径开始查找,也就是/setvlet06,不需要以斜杠开始,-->
    <!--同理,如果你有目录,你要抓住的核心点是从webapp的根开始寻找-->
    <welcome-file>login.html</welcome-file>
</welcome-file-list>

同时要注意,当web根下有index.html的时候,这时候访问web根路径就会直接访问这个index.html

这是因为Tomcat服务器为我们提前做好了,实际上有两个地方可以配置:

  • 一个是在webapp内部的web.xml文件中,这次配置属于是局部配置
  • 一个是在catalina_home/conf/web.xml中进行配置,这里配置的属于全局配置

在tomcat的源代码中可以发现

<welcome-file-list>
    <!--要注意路径:代表的当前webapp的根路径开始查找,也就是/setvlet06,不需要以斜杠开始,-->
    <!--同理,如果你有目录,你要抓住的核心点是从webapp的根开始寻找-->
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

上面的遵循的是局部优先原则。

欢迎页可以是一个servlet吗?

欢迎页就是一个资源,既然是资源,那么可以是静态资源,也可以是动态资源。

WEB-INF能否拿来做资源的分配?

放在WEB-INF下的资源是受保护,在路由器上是不能够直接进行访问的,所有像htmlcssjs文件不能放在WEB-INF

11. HttpServletRequest接口详解

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    super.doGet(req, resp);
}

要思考的问题是:

  • 这个HttpServletRequest定义为接口,那么是由谁来实现的呢?
public class RequestFacade implements HTTPServletRequest{}

Tomcat中,是基于这个RequsetFacade来进行实现的

  • 这个HttpServletRequest没有看到new,那么是谁创建并且传递的呢?

那么理所当然的,对于接口规范其必然是通过其实现子类来实现,那么谁来提供这个实现子类呢?就是web服务器,这些web服务器还提供了创建对象供方法调用的功能

  • HttpServletRequest对象都有什么信息?都包装了什么信息?

封装了http的请求协议。实际上是用户发送请求遵循了http协议,tomcat将http协议的信息和内容解析出来,封装到ServletRequest对象中,供java程序员调用。

request和reponse对象只在一次请求中有效,当下一次请求发来的时候,又是不同的对象了。

常用的方法

获取前端用户提交的数据

public String getParameter(String name);
//获取这个一维数组的第一个元素
public Enumeration<String> getParameterNames();
public String[] getParameterValues(String name);
//为什么是String[]?因为传过来的数据的key有可能会重复导致覆盖
public Map<String, String[]> getParameterMap();
//以上四个方法与用户提交的信息有关系

关于前端提交的数据一个细节:关于前端发送过来的数据格式:

假设提交了120这样的数字,其实是以字符串120这样的形式进行提交的,所以服务端获取到的肯定是一个字符串的120

关于请求域对象

requset对象又被称为请求域对象。请求域对象比应用域对象的范围小很多,生命周期短很多。

请求域对象也有这三个方法setgetremove

尽量使用小的域对象,因为小的域对象的占用的资源是比较少的,当然了也要根据业务需求来判定。

public Object getAttribute(String name);
public void setAttribute(String name, Object o);
public void removeAttribute(String name);

关于request的常用方法

public RequestDispatcher getRequestDispatcher(String path);
//多个servlet中共享数据的另一种方式

该方法用来获取获取一个请求转发器,使用方法:

如果是同一个webapp内进行跳转,只需要填写项目内的url即可

RequestDispatcher requestDispatcher = request.getRequestDispatcher("/b");
//相当于是把下一个资源的路径包装到请求转发器中,实际上是把下一个跳转的资源的路径告知给tomcat服务器
requestDispatcher.forward(request,response);
//请求转发,公用一个请求域
//转发是一次请求
//只要是tomcat服务器中的合法资源,都是可以进行转发
//路由的转发路径要注意,不加项目名,以/开始,简单来说也是从webapp根下开始寻址的
request.getRequestDispatcher("/test.html").forward(request,response);

关于两个比较容易混淆的方法:

//uri?username=zhangsan
String username = request.getParameter("username");
//之前一定执行过:request.setAttribute("username",username);
Object username = request.getAttribute("username");

这两个方法的区别在哪里?

第一个方法是从用户在浏览器上提交的数据

第二个方法获取的是请求域中绑定的数据

关于数据提交乱码的问题

/*Overrides the name of the character encoding used in the body of this request. 
This method must be called prior to reading request parameters or reading input using getReader(). 
Otherwise, it has no effect.
*/
/*
覆盖此请求正文中使用的字符编码的名称。
必须在使用getReader()读取请求参数或读取输入之前调用此方法。
否则,它没有效果。
*/
//设置请求体的字符集
//Tomcat10之后请求体当中的字符集默认就是UTF-8,但是在9之前包括9在内,如果请求体提交有中文,就会有乱码
public void setCharacterEncoding(String env) throws UnsupportedEncodingException;

设置请求体的字符集,这个方法显然是为了处理post请求的乱码,因为get请求的乱码是在uri也就是在请求行引发的乱码。

同样的,如果我们想要回显页面数据,低版本下也需要对response中的charSet进行指定,也就是

response.setContentType("text/html;charset=UTF-8");

get请求如何解决?

String username = req.getParameter("username");
byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
username = new String(bytes,StandardCharsets.UTF_8);
<Connector URIEncoding="UTF-8"></Connector>

12. 请求转发和重定向的区别

在web应用中应该如何完成资源的跳转?

  • 第一种方式:转发
request.getRequestDispatcher("/test.html").forward(request,response);
//转发的时候是一次请求,不管你转发了多少次,都是一次请求
//比如说A转发给B,B转发给C,C又转发给D
//这是因为调用forward方法的时候,会将当前的request和reponse传递给下一个servlet\
//由Tomcat做的
  • 第二种方式:重定向
reponse.sendRedirect("/项目名/b");//"reponse.getContextPath()+/b"
//为啥要加项目名?因为其实这个动作是浏览器做的,对于请求而言,浏览器肯定需要知道你的项目名!
//完成资源的跳转
//这个就相当于什么呢,就是把这个response传回去浏览器
//但是这时候还多做了一件事情,就是给浏览器一个消息说再发一次请求,然后请求路径是给你指定的那个路径
//然后浏览器进行一次资源的内部跳转(自发完成的)
//因此浏览器因为这次重定向而发送了两次请求
//由浏览器做的

形式上有什么区别?

转发:比如我从ServletA转发到ServletB,访问A下的a资源,转发到B下的b资源,最终浏览器的地址栏上保留下/b

重定向:会导致地址栏上的地址发生改变

如何确定什么时候使用转发?什么时候使用重定向

如果在上一个Servlet中绑定了数据,希望在下一个Servlet中使用数据,这种情况下使用转发

剩下的均使用重定向。

13. 注解开发原理

//1.获取类对象
Class<?> clazz = Class.forName("servlet01.TestJava");
//2.获取类的注解对象
//先判断是否有注解对象
//是否有一个注解
if(clazz.isAnnotationPresent(WebServlet.class)) {
    //如果有,则获取
    WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
    //获取这个注解的信息
    System.out.println(webServlet.name());
    String[] s = webServlet.value();
    for (String s1 : s) {
        System.out.println(s1);
    }
    //这样的话就能够获取注解内的信息了
    //然后拿到value和name之后就能够进行对应信息的解析了

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录