JSP/Servlet 学习笔记

Servlet/JSP 疑难汇总:

Web 容器:

什么是Web 容器?

  • 前提:我们知道servlet可以理解服务器端处理数据的java小程序,那么谁来负责管理servlet呢?这时候我们就要用到web容器。它帮助我们管理着servlet等,使我们只需要将重心专注于业务逻辑。
  • servlet没有main方法,那我们如何启动一个servlet,如何结束一个servlet,如何寻找一个servlet等等,都受控于另一个java应用,这个应用我们就称之为web容器
  • 我们最常见的tomcat就是这样一个容器。如果web服务器应用得到一个指向某个servlet的请求,此时服务器不是把servlet交给servlet本身,而是交给部署该servlet的容器。要由容器向servlet提供http请求和响应,而且要由容器调用servlet对象的方法(servlet实例也是由容器来创建出来的),如doPost或者doGet。
  • servlet 是由容器来进行管理的。 比如:如何加载类,实例化和初始化servlet,调用servlet方法,并使servlet实例能够被垃圾回收。
  • 容器会自动为接收的每个servlet请求创建一个新的java线程,servlet运行完之后,容器会自动结束这个线程。
  • 利用容器提供的方法,你可以简单的实现servlet与web服务器的对话。否则你就要自己建立server,监听端口,创建新的流等等一系列复杂的操作。而容器的存在就帮我们封装这一系列复杂的操作。使我们能够专注于servlet中的业务逻辑的实现。

Jetty 和 Tomcat 有何区别?

  • 概念:Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,因为Tomcat技术先进、性能稳定,而且免费,因而深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器。
  • 概念:Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。
  • Jetty更轻量级。这是相对Tomcat而言的。由于Tomcat除了遵循Java Servlet规范之外,自身还扩展了大量JEE特性以满足企业级应用的需求,所以Tomcat是较重量级的,而且配置较Jetty亦复杂许多。但对于大量普通互联网应用而言,并不需要用到Tomcat其他高级特性,所以在这种情况下,使用Tomcat是很浪费资源的。
  • Jetty更灵活一些。体现在其可插拔性和可扩展性,更易于开发者对Jetty本身进行二次开发,定制一个适合自身需求的Web Server。
  • Tomcat目前应用比较广泛,对JavaEE和Servlet的支持更加全面,很多特性会直接集成进来。当支持大规模企业级应用时,Jetty便需要扩展,在这场景下Tomcat便是更优的。
  • Jetty默认采用NIO结束在处理I/O请求上更占优势,在处理静态资源时,性能较高,Jetty默认采用NIO结束在处理I/O请求上更占优势,在处理静态资源时,性能较高

Tomcat 和 apache 以及 nginx 如何配合使用?

  • Nginx(Web服务器,反向代理服务器)优点:负载均衡、反向代理、处理静态文件优势。nginx处理静态请求的速度高于apache。
  • Apache(Web服务器)优点:相对于Tomcat服务器来说处理静态文件是它的优势,速度快。Apache是静态解析,适合静态HTML、图片等。
  • Tomcat(Java Web容器):动态解析容器、处理动态请求,是编译JSP、Servlet的容器,Nginx有动态分离机制,静态请求直接就可以通过Nginx处理,动态请求才转发请求到后台交由Tomcat进行处理。 Apache在处理静态文件有优势,Nginx并发性比较好,CPU内存占用低,如果rewrite频繁,那还是Apache较适合。
  • 简单来说: Web 容器就是用来动态处理访问数据的,而一个成熟的网站网页要进行展示同时需要大量的静态资源,这些静态资源一般由性能比较高的Web代理服务器来进行应答。
Servlet 的相关知识点:

Tomcat 运作原理,如何加载servlet 处理客户请求?(源码级别!)

参考:JavaWeb——Servlet(全网最详细教程包括Servlet源码分析)

这篇博客解释了Servlet 的运作方式,证明了我的思路是对的:调用Servlet进行运作的正是我们设置的Web容器/Servlet容器(Tomcat/Jetty),请求和相应(Request & Response)也都是由Web容器/Servlet容器来封装好的。

Servlet 的生命周期:

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • 加载和实例化:当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。Web容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法)
  • 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次(Servlet的运作机制是:单例多线程)。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。
  • 处理请求Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后(并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法),调用ServletResponse对象的方法设置响应信息
  • 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法(仅会执行一次)。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。
  • 需要注意的是:在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。

Servlet 是如何处理用户请求的?

Servlet示例代码:

@WebServlet(urlPatterns ={"/HelloWorldServlet","/Haha","/Hehe"})
public class HelloWorldServlet extends HttpServlet {

    public HelloWorldServlet() {
        System.out.println("HelloWorldServlet的构造方法()");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行了HelloWorldServlet的service()...");
        super.service(req, resp);
    }

    //处理客户端的get请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行了HelloWorldServlet的doGet()...");
    }

    //处理客户端的post请求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //因为这里并没有给客户端返回任何的内容,那么客户端也没有输出任何内容。
        //resp.setContentType("text/html;charset=utf-8");
        resp.setContentType("text/plain;charset=utf-8");
        PrintWriter out = resp.getWriter();

        System.out.println("执行了HelloWorldServlet的doPost()...");
        out.println("<h1>我是服务器响应</h1>");
        out.flush();
        out.close();
    }

    //查询下如何正常的关闭jetty服务器,正常关闭才能显示destory.
    @Override
    public void destroy() {
        System.out.println("HelloWorldServlet的destroy()");
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("HelloWorldServlet的init()");
    }
}

若开启Web容器后,在第一次得到用户的Get的请求时(没有启动预加载模式),则会出现如下的结果:(默认是一种懒加载)

注意:阶段1只在第一次访问的时候执行,执行过以后就加载到内存了,由于Servlet的执行模式是:单例多线程,所以后面若是访问相同的servlet,则直接调用该对象的doService()进行服务即可。
在这里插入图片描述

开启了预加载模式,则会按照如下情况加载函数:

在 Web容器 启动的时候,就会执行第一阶段的:类的构造函数和init()初始化方法,在以后的请求中(包括第一次请求)都不会再调用第一阶段的方法了,只会从第二阶段开始执行。

预加载模式如何开启?有何作用?

开启方法: 在注解中添加一个loadOnStartup属性,数字越小优先级别越高。预加载。

Servlet代码如下:

@WebServlet(urlPatterns ={"/HelloWorldServlet","/Haha","/Hehe"},loadOnStartup = 1)
public class HelloWorldServlet extends HttpServlet {

    public HelloWorldServlet() {
        System.out.println("HelloWorldServlet的构造方法()");
    }

预加载有何意义?:个人认为,即便Servlet的默认工作方式是:单例多线程模式,这些不同的类只会被加载并初始化一次,但是有些情况下,我们追求极致的体验,可能这个类加载的数据较多,较为复杂,还要执行一些检查等等之类的操作,会很影响用户体验(可能因为第一个人加载的时候,大家都在等待他加载),所以我们使用预加载模式,就可以避免这种情况的发生,在服务器启动的时候,自动地将不同的servlet对象加载到内存中,当用户请求进来时,直接调用相应servlet对象的doService()方法进行处理即可!

Servlet 的单实例多线程工作方法的原理

注意:这里面的Servlet单实例,指的是对应的请求路径的Servlet实例,不同的请求路径对应的是不同的Servlet实例,但在相同的请求路径的时候,应用的都是同一个Servlet实例,但有多个线程去应用这个Servlet对象。调用其中的方法!(Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器启动时被加载(被创建一个servlet实例对象)。且只加载一次

如何来证明 servlet 是单例模式?

package com.xbky.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "RegServlet", value = "/RegServlet")
public class RegServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*怎么证明servlet处理用户的请求是单例多线程呢?*/
        /*
        * 怎么证明是单例的呢?
        * */

        System.out.println(this.hashCode()); //这个hashCode值如果没有发生改变说明是单例的。
        /*
        * 怎么证明它是多线程
        * 不同的线程线程名字肯定是不同的。
        * */

        System.out.println(Thread.currentThread().getName()); //打印当前线程的名字

        String username = request.getParameter("username");
        String email = request.getParameter("email");
        System.out.println(username);
        System.out.println(email);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //让doGet 去调用doPost,说白了,就是doGet和doPost做的事情一样的。
        doPost(request, response);
    }
}

注意:利用不同的浏览器,在Web领域中对于服务器来说这两个不同的浏览器就相当于两个不同的客户端,会被当成两个用户发起的请求来处理!

结论: 我们通过不同浏览器去访问同一个servlet,最后得到的结果是:两次请求造成在服务器上打印的this.hashCode()是一样的,但是 获得的Thread.currentThread().getName()是不同的!

Servlet 的单例多线程工作模式造成了多线程同步的安全问题?

  • service()方法为Servlet的核心方法,客户端的业务逻辑应该在该方法内执行,典型的服务方法的开发流程为:
Created with Raphaël 2.2.0 解析客户端请求 执行业务逻辑 输出响应页面到客户端
  • 为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性
  1. 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题

  2. 当服务器接收到来自客户端的多个请求时,服务器会在每一个单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。

  3. 请大家注意,虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式

访问方式的分析如下:
i. 如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。

ii. 如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。

iii. 如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。

iv. 如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。

v. 如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。

过滤器的工作原理是什么?它与监听器(Listener)、Servlet 有何联系?

问题:在doGet请求中,如果调用doPost{}的方法,其参数也能传递过去?(答案是:可以的!)

原因:个人认为是因为, 不管是POST请求还是GET请求的参数,都会被Web容器(Jetty/Tomcat)封装成request对象,同时Web 容器还会创建一个 response对象。将这两个对象一同传递给 servlet 的 doService()方法。所以,不管是是GET请求的参数还是POST请求的参数,他们都被封装成同一种对象了,然而:这个对象获取相应键值的方式都是一致的

单例模式:

单例模式是什么?

概念:单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

特点:

  • 在任何情况下,单例类永远只有一个实例存在

  • 单例需要有能力为整个系统提供这一唯一实例

为何要有单例模式?

  • 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

  • 是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息

单例模式的懒汉式:

概念: 懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”

package org.mlinge.s02;
 
public class MySingleton {
	
	private static MySingleton instance = null;
	
	private MySingleton(){}
	
	public static MySingleton getInstance() {
		if(instance == null){//懒汉式
			instance = new MySingleton();
		}
		return instance;
	}
}

这里实现了懒汉式的单例,但是熟悉多线程并发编程的朋友应该可以看出,在多线程并发下这样的实现是无法保证实例实例唯一的,因为这里没有考虑到同步,很多线程可能会同时进入到if()条件中去,下面我们就来看一下多线程并发下的执行情况,这里为了看到效果,我们对上面的代码做一小点修改:(还可以同步加同步语句来保证其安全性参考博客!)

package org.mlinge.s02;
 
public class MySingleton {
	
	private static MySingleton instance = null;
	
	private MySingleton(){}
	
	public static MySingleton getInstance() {
		try { 
			if(instance != null){//懒汉式 
				
			}else{
				//创建实例之前可能会有一些准备性的耗时工作 
				Thread.sleep(300);
				instance = new MySingleton();
			}
		} catch (InterruptedException e) { 
			e.printStackTrace();
		}
		return instance;
	}
}

单例模式的饿汉式:

概念: 饿汉式单例是指在方法调用前,实例就已经创建好了。

代码:

package org.mlinge.s01;
 
public class MySingleton {
	private static MySingleton instance = new MySingleton();
	
	private MySingleton(){}
	
	public static MySingleton getInstance() {
		return instance;
	}
}

以上是单例的饿汉式实现,我们来看看饿汉式在多线程下的执行情况,给出一段多线程的执行代码:

package org.mlinge.s01;
 
public class MyThread extends Thread{
  	
	@Override
	public void run() { 
		System.out.println(MySingleton.getInstance().hashCode());
	}
	
	public static void main(String[] args) { 
		
		MyThread[] mts = new MyThread[10];
		for(int i = 0 ; i < mts.length ; i++){
			mts[i] = new MyThread();
		}
		
		for (int j = 0; j < mts.length; j++) {
			mts[j].start();
		}
	}
}

执行结果:

1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954

从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。

反射和注解:

通过反射来加载实例,以及自己主要加载类的实例有何不同之处?不能够调用有参构造吗?

参考:Java反射学习

反射主要体现的是动态性,但在Java上反射的动态性体现的也比较有限!(最多只能获取对象的信息和利用反射来读取注解中的相关信息等,或者修改权限等~)

在Spring IOC注入中,java bean有一个约定俗成的习惯,就是要有一个无参构造器。当然也可以不写,但是一般还是建议写的,原因如下:

  • java web开发中spring是很常用的,其IOC利用了java的反射,而spring的反射要求这个bean必须要有一个无参构造器。(多嘴一句,并不是说反射一定要有无参构造器,但是spring显然是只能由无参构造器创建新对象的

  • 如果没有声明无参构造器,那么所有继承该类的类都需要实现其有参方法,在很多时候并不如getter和setter方便。

为何要在Java Web的开发阶段频繁使用注解,其意义何在?

注解的好处

  • 注解的引入可以使我们能够使用编译器来验证格式,并存储程序额外信息。
  • 注解又称元数据,为我们在代码中添加信息提供了一种方法,使得我们能够在稍后某个时刻方便的访问这些数据。
  • 注解在一定程度上是将元数据和源代码文件结合在一起,而无需使用额外的配置文件进行管理。

常见的注解处理方案主要包括应用于:(如何使用注解)

  • SOURCE 阶段的 apt(annotation proccess tools):可以用做代码生成器来生成代码(做一些机械性的代码工作)
  • 应用于 RUNTIME 阶段的反射。(作用类似于Spring中的Path地址映射)

RUNTIME 阶段的反射

  • Java5 中对反射相关的接口进行扩展,用以通过反射机制获取 RUNTIME 阶段的注解信息,从而实现基于反射的注解处理器
  • 参考:揭开 Java 注解的神秘面纱,一文中详细解释了注解的两种用法, 并且写另一个例子:Spring 基于反射的 RequestMapping 注解的实现过程

Java 的参数中有@,这个注解符号是干嘛用的?注解还能当做参数进行传递吗?

注解可以作用在地方:

  • Method 方法
  • Class 类型
  • Field 字段
  • Package 包
  • Parameter 参数(所以:我们可以将注解作用在参数上,不是将注解当做参数传递,而是利用注解去修饰参数。当然最后也能通过参数来获取这些注解,从而应用这些注解!)
  • AnnotatedParameterizedType 泛型
  • AnnotatedTypeVariable 变量
  • AnnotatedArrayType 数组类型
  • AnnotatedWildcardType
Web请求

如何设置请求的路径匹配规则?

servlet的URL匹配规则:
A.精确匹配 例如: /servlet/HelloWorldServlet
B.路径匹配: 例如: /abc/*
C.扩展名匹配:例如: .action
D.默认匹配: /
E.过滤器,过滤所有的请求: /

以下是一种路径匹配规则的示例:
注意:路径匹配和扩展名匹配不能混用。(此时的访问路径是:localhost:9000/helloservlet/abc)
其中:helloservlet 是在部署Jetty/Tomcat时,设置的虚拟映射路径,那个路径就成了项目的根路径。

@@WebServlet(name = "TestServlet",value="/abc")
public class TestServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("TestServlet的doPost()...");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("TestServlet的doGet()...");
    }
}

问题:如何设置访问路径能够使得进入站点的时候,初始默认访问一个servlet,找不到的都访问另一个servlet呢?

参考:Servlet 路径匹配的优先顺序
注意:上面说的完全匹配路径中,如果想设置访问项目根域名时访问的是servlet(之前都是该项目下的一个默认的index.jsp/html),需要如下设置:(@WebServlet(value = “”),只有这样设置,才算是完全匹配到项目的根路径了,而不能设置为"/")

代码示例:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(value = "")   核心代码!
public class ServletTest extends javax.servlet.http.HttpServlet {
    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        doGet(request,response);
    }


    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {

        System.out.println("访问成功!");
        // 相应的类型:
        response.setContentType("text/html");
//        response.setCharacterEncoding("utf-8");

        // 获取相应的输出流:
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello world!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>你好</h1>");
        out.println("</body>");
        out.println("</html>");

    }
}

请求重定向和请求转发有什么区别?

相同点:

  • 页面都会实现跳转

不同点:

  • 请求转发的时候,url地址不会产生变化;状态码:307(Request和Response对象没有重新生成,而是被递交给了下一个servlet
    在这里插入图片描述

  • 重定向时候,url地址栏会发生变化; 状态码:302 (重新访问了一次页面,需要经过Web容器处理请求(重新生成Request和Response对象)并递交给相关Servlet处理)
    在这里插入图片描述

get请求和post的请求的区别

JSP 相关内容:

JSP九大内置对象是什么?

常用的JSP对象有哪些?

如何获取这些JSP对象?

这些JSP对象的作用范围有何不同之处?(案例演示)

EL表达式和JSP表达式的运行机制是什么?

${}表达式是什么?为何能够使用session

网页中的编解码相关问题:

Unicode 字符集是用4个字节表示还是用1~4个字节进行表示?

概念:

  • Unicode 是全球文字统一编码。它把世界上的各种文字的每一个字符指定唯一编码,实现跨语种、跨平台的应用。
  • Unicode只是一个符号集,它只规定了每个符号的二进制数,却没有规定这个二进制数应该如何存储。
  • 比如,汉字‘严’的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

在Java体系中的Unicode和UTF-8的问题?

参考:Java中弄懂Unicode和UTF-8编码方式

这一篇文章中提到了几个重要的概念:

  • Java中的Char,String 等变量,其在JVM内部,用到都是Unicode编码来进行标记的。
  • Java中的类型都有其规定的存储位数,Char(两个字节,最多只能表示两个字节长度的编码)
  • 如果一个字符超过了两个字节的表示长度,那么就无法将它所对应的Unicode编码放到Char类型的变量中去了,需要存放到类似于String这样的类型当中去。
  • 核心:为何不直接用Unicode编码中对应的二进制来存储我们要保存的字符文件?因为:即便这样存了,你在读取的时候,根本不知道该如何来读取这个二进制,该以几个字节来读,如果都以4个字节来保存,确实能够读取出来,但又会造成空间浪费,所以才出现了UTF-8这种可变长编码,要看懂Unicode和UTF-8的映射关系。

参考2:彻底弄懂 Unicode 编码

表示编码和存储编码的区别?Unicode本身也可以做为一种编码的方式进行存储吧?

回答:

  • 个人认为运用”表示编码“和”存储编码“这样的说法来区分Unicode和UTF-8其实并不是很准确,Unicode本身就标记了某一个字符在Unicode编码表中对应的2进制的数值。
  • 我们一般都说Unicode是一种表示,而不是编码,但是在很多编辑器保存的时候,却能将该文本的编码格式设置为:Unicode。
  • 个人认为:这只是用了Unicode中最大的字符位数:4个字节来表示所有的字符,这样做虽然浪费的磁盘空间(对于涵盖大量英文字符的文本来说),但确实能造成不冲突。
  • 但我们一般不会用这种编码格式(Unicode)来存储我们的文件,我们用的是UTF-8,他通过将Unicode进行对应的编码转换,造就了一种可变长的编码字符串,对于含盖大量英文的文件来说,节约了其存储空间。

用户表单提交(POST请求)的数据中有中文,造成Web 容器的乱码如何解决?(Tomcat),Get请求的中文乱码如何解决?

概念:

  • 将字符转换为字节的方式称为编码(Encode)

  • 将字节转换为字符的方式称为解码(Decode)

参考:Servlet中文乱码原因 解决 Get 和 Post 和客户端

注意:这篇文章原理和解决方法都没有问题,就是有一个问题:作者将编码和解码的概念搞混了(在某些回答中),其解决方法都是都是正确的!

返回页面的编码格式问题造成中文乱码的原因?如何解决

核心:在Maven-web项目中,response返回的结果的字符编码格式应该是由Web容器来设置的(web容器创建的这个response对象,并且以它作为参数来调用servlet中的doService()函数),因为生成request和response对象的正是我们的web容器的工作啊!

在默认的Tomcat服务器运行下返回的Response Header内容

HTTP/1.1 200
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 85
Date: Sat, 15 Feb 2020 07:46:41 GMT
Keep-Alive: timeout=20
Connection: keep-alive

在默认的Jetty服务器下返回的Response Header内容

HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 89
Server: Jetty(9.4.0.v20161208)

我们也可以通过设置 servlet 中使用到的response对象中的编码方法,从而控制返回页面的Response Header 中的相关信息!

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(value = "")
public class ServletTest extends javax.servlet.http.HttpServlet {
    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
        doGet(request,response);
    }


    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {

        System.out.println("访问成功!");
        // 相应的类型:
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8"); // 核心语句!!!!


        // 获取servletContext
        ServletContext servletContext = this.getServletContext();
        // 获取相应的输出流:
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello world!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>你好</h1>");
        out.println("<h1>"+servletContext.getAttribute("name")+"</h1>");
        out.println("</body>");
        out.println("</html>");

    }
}
数据库使用的相关问题:

JDBC获取被插入数据记录的主键id值:

重点JDBC 获取被插入数据的主键ID值

Session 和 cookie 技术:

Servlet 中的 sesssion如何使用?

session 的运作原理:(HTTP 是无状态的协议)

问题:会话和请求的区别?

Session 在一个会话中持续存在,那么服务器是如何辨认出一个个会话的呢?

回答:通过Session id, 服务器通过客户端请求中所带有的sessionid来辨这次请求属于哪次会话下的请求,从而这些带有相同sessionid的请求都属于同一个会话中的请求,隶属于同一个会话。

Session id 是如何生成的?

(服务器生成session对象,并将sessionid 发送给客户端的这个逻辑好像并没有明确写出来,应该是在源码库中?)

servletContext 是什么?它跟Session 有什么关系?

HTTP 相关内容:

关于HTTP 网页请求头以及相应头部各个指标的解析?

参考:HTTP请求头

注意: 最好自己去看一下《图解HTTP》这本书,这里面的头部信息真的是非常复杂,记住常用的就好了!

网页响应状态码的分类及其意义?

问题:关于Web访问流程时,什么时候会去直接利用缓存页面展示(304 Not Modify),而不是向服务器发起一个请求来获取页面呢?怎么做到的?

问题:关于域名解析服务的问题,DNS应该有很多层级,我们的运营商会不会也有相关的域名解析服务? 或者说:交换机和路由器有没有这种功能呢?只能去指定的DNS服务器中去查询吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值