Java Web基础学习——Servlet

本文参与「少数派读书笔记征文活动」https://sspai.com/post/45653

上个月实训的时候,跟着老师学了点JavaWeb,虽然老师要求的功能是完成了,但其实心里一直很模糊,因为代码大多是自己码出来的,但是用法都不清楚,项目不是一个完整的项目,很多功能没有实现,总觉得心里有点不舒服,所以在学python的间歇希望能够完成它。

参考文章:
https://blog.csdn.net/jiangwei0910410003/article/details/22728051
https://www.cnblogs.com/mujingyu/p/7874797.html
https://www.cnblogs.com/LipeiNet/p/5699944.html

遇到的问题:

1.MyEclipse 2017 CI添加tomcat服务器:Windows->Perferences->Servers->Runtime Environment->Add(Tomcat的解压文件),下载地址:http://tomcat.apache.org/download-80
2.MyEclipse 2017 CI查看源代码:将光标置于方法或类名中,右键Open Declaration即可
注意:出现“The jar file has no source attachment”错误:这种情况一般是因为缺少source.jar或者src.jar,所以解决方案如下:我的情况是javaee-api-7.0.jar没有对应source.jar(MyEclipse自带的jdk包含source.jar并且配置好了source attachment,然而D:\Program Files (x86)\MyEclipse 2017 CI\plugins插件文件夹下的jar包则没有,同理你自己在网上另找的jar也没有);在Workspace下找到JavaEE 7.0 Generic Library,右键点击javaee-api-7.0.jar->properties->External location->Path选择javaee-api-7.0-sources.jar的路径,然后OK,重启软件即可
备注:
(1)各类Jar File下载地址(包括对应的source.jar):http://www.java2s.com/Code/Jar/CatalogJar.htm(利用索引查找就好,搜索框搜索好像需要FQ)
(2)javaee-api-7.0.jar里面是.class文件;javaee-api-7.0-sources.jar里面是.java文件
(3)JAVA SE=JDK(开发Java程序)+JRE(运行Java程序)——标准开发;JAVA EE——Web开发;JAVA ME——移动端开发(嵌入式开发)

**************************************************************分界线**************************************************************

昨天早上开开心心的准备学习,结果我第一个代码就让我弄了一上午,心累到放弃,最后我心里暗自立下flag,我再也不想看到MyEclipse了!!!一个不到30行的代码,第一次运行ok,第二次运行ok,第三次,呵呵了,代码没报错,编译没报错,连警告也没有,可是当我用tomcat运行的时候,永远停留在launch client不动了。代码太多?项目太大?no,no,no,当前工程只有3个均不到30行的代码,而且另外两个能完美运行(三份代码其实都差不多),而且我在网上找的一个商城项目也能运行。servlet映射不对?我看了发布的servlet,路径绝对没错,我的web.xml也没错;重启?重启了好几次,有次重启,莫名其妙一个java文件中多了一行代码???另一次,web.xml多了好多好多行注释,而且第一行注释说“最好不要删除这个注释”???而且格式也动了。反正我一上午试了无数种方式,最后,呵呵,拜拜,MyEclipse;换了Idea,简单配置好后,把src文件原样复制到工程下面,运行正确。。。。。。而且!!之前运行正常的java文件输出结果与MyEclipse不一样,原来我之前的理解没有错。。。
再见,MyEclipse,不,再也不见。。。。,进入正题。。。。
*************************************************************分界线***************************************************************

Idea(IntelliJ IDEA 2018.02)配置:
1、通常点四处地方,自己去配置:(1)File->Settings(2)File->Project Structure(3)工程右键->Open Module Settrings(4)Run->Edit Configurations
2、配置好tomcat后,External Libraries会自动添加tomcat里面的jar包;但是对于普通的java文件(不通过tomcat运行),要使用另外添加的jar包:Run->Edit Configurations->Application->Use classpath of module(勾选Include dependencies with "Provided" scope)或者.java文件文件右键->Edit Scopes
3、非正常关闭软件再打开运行tomcat通常会提示,xxxx端口被占用:cmd->查看占用端口的进程(netstat -aon|findstr 端口号)->关闭进程(利用进程ID:taskkill -f -pid 进程ID)

Servlet

Servlet是一个能运行在服务器端的java代码,java EE说明书:https://docs.oracle.com/javaee/7/api/toc.htm
其实Servlet是一个接口,他有三个实现类:FacesServlet、GenericServlet、HttpServlet,常见的五个方法:

  • void destroy():是在Servlet被销毁的时候调用
  • ServletConfig  getServletConfig():包括初始化和启动Servlet变量
  • String  getServletInfo():获取Servlet的相关信息,比如作者、版本和版权
  • void init(ServletConfig  config):在Servlet被初始化的时候调用(相当于类的初始化函数)
    注意:
    1、如任何一个普通的java类一样,编译器会自动生成一个构造函数;构造函数和init()都会被Web容器(tomcat)调用,而且是先调用构造函数,但容器只会调用默认构造函数,当你自己写了构造函数后,容器初始化会出错(我就是在继承HttpServlet的类中写了构造函数,编译报错:“找不到init()”,但其实我两个都写了的,后来把构造函数删了就正常了),所以任何时候都不推荐自己写构造函数来初始化servlet
    2、其实同时还存在无参数void init();tomcat默认调用init(ServletConfig  config)并且传过来一个参数config,既然如此,init()就多余了?其实init()是为了防止程序员重写init(ServletConfig  config)时忘记写super.init(ServletConfig  config),容易造成空指针异常,所以这就要求我们最好不要重写init(ServletConfig  config)
  • void service(ServletRequest req,ServletResponse res):回应客户端请求时调用

Servlet的生命周期:

当服务器启动的时候,服务器会加载所有的Web应用,当用户在浏览器中第一次请求Servlet的时候,init()被调用,此时Servlet被创建,因为Servlet是用的单例模式,所以只要Servlet所在的应用没有关闭或者服务器没有关闭,这个Servlet始终都是在服务器的内存中,所以当你再一次请求这个Servlet的时候init()不会被调用。

当用户每次请求Servlet的时候,这个Servlet的service()都会被调用

当该Web应用被关闭或者服务器关闭了,这个Servlet才会被销毁,此时destroy()被调用,同时这个单例的Servlet也会从内存中消失。

Servlet接口的子类——GenericServlet(通用Servlet):

定义了一个通用的,与协议无关的Servlet,它实现了Servlet接口中的所有方法,同时自己也是添加了几个方法:

  • String  getInitParameter(String name):这个方法与ServletConfig对象相关,通过name来从ServletConfig对象中获取value
  • Enumeration<String>  getInitParameterNames():同样与ServletConfig对象相关,是获取ServletConfig对象中所有name属性的枚举集合
  • ServletContext  getServletContext():获取正在运行的Servlet的ServletContext对象
import java.io.*;
import javax.servlet.*;
 
public class FirstServlet extends GenericServlet
{
    @Override
    public void service(ServletRequest req,ServletResponse res) throws ServletException,java.io.IOException
    {
        res.getOutputStream().write("hello servlet".getBytes());
    }
}

GenericServlet的子类——HttpServlet

HttpServlet就是针对于Http协议的Servlet,它实现了GenericServlet中的抽象方法,其中的doXXX()就是对应不同的Http协议请求方式,只有get/post方式现在最常用,其他的方式都被弃用了;所有的请求都会调用service(),service()的工作机制其实很简单:首先通过HTTP对象中的getMethod()方法获取客户端的请求方式,然后进行判断执行对应方法,所以我们在编写Http协议的Servlet的时候,只需要继承HttpServlet,实现其doGet()和doPost(),下面是一个实例:

package com.lovebaobao.servletconfig;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

//配置参数(初始化信息)
@WebServlet(name = "ServletConfigDemo",
    urlPatterns = "/servlet/ServletConfigDemo",
    initParams = {
        @WebInitParam(name="url",value="http:baidu.com"),
        @WebInitParam(name="user",value="baozi"),
        @WebInitParam(name="password",value="123456")
    })
public class ServletConfigDemo extends HttpServlet {

    /*
    简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的
    字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,
    否则就会出现序列化版本不一致的异常。(InvalidCastException)
    serialVersionUID有两种显示的生成方式:一个是默认的1L,比如:private static final long serialVersionUID = 1L;一个是
    根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;
     */
    /*
    序列化:指把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输),
    这个过程称为序列化。通俗来说就是将数据结构或对象转换成二进制串的过程
  反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生
    成的二进制串转换成数据结构或者对象的过程。
     */
    private static final long serialVersionUID=1L;  //为了在反序列化时,确保类版本的兼容性
    
    //ServletConfig对象是封装init_param数据
    //在实际开发中,有一些不适合在servlet程序中写死的数据,这些数据就可以通过配置方式配给servlet,
    //例如:servlet采用哪个码表,servlet连接那个数据库,servlet读取哪个配置文件等信息
    public ServletConfig config=null;

    @Override		//重写;当你不小心重载(不是重写)时编译器会生成错误信息
	public void init(){
		this.config=this.getServletConfig();
	}
//    public void init(ServletConfig config){           %这样写也可以运行,但是容易造成空指针错误1
//        this.config=config;
//    }

    @SuppressWarnings("unchecked") //允许选择性地取消特定代码段(类或方法)中的警告
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //得到所有的初始化参数key和value
        Enumeration<String> e=config.getInitParameterNames();
        while(e.hasMoreElements()){
            String name=e.nextElement();
            System.out.println("name"+name);
            System.out.println("value:"+config.getInitParameter(name));
            System.out.println("******************");
        }
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

输出:
namepassword
value:123456
******************
nameuser
value:baozi
******************
nameurl
value:http:baidu.com
******************

备注:可见ServletConfig对象干的事就是将Servlet的初始化信息封装起来然后在代码中读取

ServletContext对象

ServletContext是很重要的一个对象(其实是一个接口),同时也是四大域对象之一的context域对象,从名字上我们就知道context是上下文的意思,所以这里的context就代表整个Web应用,所以他的生命周期是:当服务器启动的时候会为每一个Web应用创建一个ServletContext对象,当Web应用销毁或关闭服务器,这个对象也就随之销毁,它的生命周期是最长的

  • Object  getAttribute(String name):得到Servlet容器中对应name的value值
  • void  getAttribute(String name,Object object):将值保存在context域中,这些值可以被web应用中所有servlet和jsp访问
  • void  removeAttributes(String name):从context域中除去对应属性值
  • String  getInitParameter(String name):获取Web应用初始化信息值
  • Enumeration<String>  getInitParameterNames():获取Web应用所有初始化name枚举
  • int  getMajorVersion():获取Servlet的最大版本
  • int  getMinorVersion():获取Servlet的最小版本
  • String  getMimeType(String fileName):通过文件名获取此文件的MiME类型
  • RequestDispatcher   getRequestDispatcher(String path) :可以实现转发机制
  • InputStream   getResourceAsStream (String path):返回path中的资源作为InputStream对象
  • String  getRealPath(String path):通过给定的path返回这个文件所在磁盘的真实路径

@WebServlet中配置的参数是局部参数,只有当前Servlet可以获取;如果要使初始化配置对所有Servlet都有效则应该配置全局参数,即将信息配置到ServletContext;由此web.xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <context-param>
        <param-name>url</param-name>
        <param-value>http://baidu.com</param-value>
    </context-param>
    <context-param>
        <param-name>user</param-name>
        <param-value>baozi</param-value>
    </context-param>
    <context-param>
        <param-name>password</param-name>
        <param-value>123456</param-value>
    </context-param>

</web-app>

实例如下:

Servlet1

package com.lovebaobao.servletconfig;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
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;
import java.util.Enumeration;

@WebServlet(name = "Servlet1",
    urlPatterns = "/servlet/Servlet1",
    loadOnStartup = 1)
public class Servlet1 extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf-8");     //指定解析编码方式
        response.setCharacterEncoding("utf-8");     //指定编码方式

        response.getWriter().append("Served at: ").append(request.getContextPath());	//输出项目名
        ServletContext context=this.getServletContext();
        response.setHeader("expires", "-1");    //没有缓存

//        context.setAttribute("data","Hello Servlet");
//        RequestDispatcher rd=context.getRequestDispatcher("/servlet/Servlet2");
//        rd.forward(request,response);   //进行转发

        Enumeration<String> names=context.getInitParameterNames();
        while(names.hasMoreElements()){
            String name=names.nextElement();
            System.out.println(name=""+context.getInitParameter(name));
        }
        //System.out.println("\n");
        RequestDispatcher rd2=context.getRequestDispatcher("/servlet/TestPath");
        rd2.forward(request,response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

Servlet2

package com.lovebaobao.servletconfig;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
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 = "Servlet2",
urlPatterns = "/servlet/Servlet2",
loadOnStartup = 1,
initParams = {
        @WebInitParam(name="name",value="小明"),
        @WebInitParam(name="location",value="南极")
})
public class Servlet2 extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());	//获取项目名
        ServletContext context=this.getServletContext();
        //获取属性值
        String data=(String)context.getAttribute("data");
        //打印
        response.getWriter().write(" "+data);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

网页输出:
Served at:  Hello Servlet
控制台输出:
123456
baozi
http://baidu.com

注意:一个Servlet只能转发一次,如果存在多个转发,只会执行遇到的第一个转发

下面来看一下各种路径的获取以及资源加载的三种方法(getResourceAsStream(String path)    getRealPath(String path)   getSystemResourceAsStream(String path) )

首先看一下web应用的项目结构:

分别在src目录和com.lovebaobao.servletconfig包中建立一个db.properties属性文件:IDEA中.properties文件是Resource型文件,故创建新的文件类型应该是Resource Bundle类型,然后将此文件编译后,可以在out/production对应项目的区域中看到相应的文件;而out/artifacts下的文件则是项目发布后产生的。
备注:Java的Properties类 属性映射(properties map)是一种存储键/值对的数据结构,通常用来存放配置信息,各种语言都有自己所支持的配置文件。此类是线程安全的:多个线程可以共享单个Properties对象而无需进行外部同步。几个主要方法:

  • String  getProperty(String key):根据键值搜索属性值
  • void  load(InputStream inStream):从输入流中读取属性列表(键和元素对)
    void  load(Reader reader)
  • Object  setProperty(String key,String value):底层调用Hashtable的方法put;它通过调用基类的put方法设置键対值
  • void  store(OutputStream out,String comments):将Properties表中的属性列表(键和元素对)写入输出流
    void  store(Writer writer,String comments)
  • void clear():清除此hashtable(即所有键值对),该方法由基类Hashtable提供

1、向properties写入/读取数据

package com.lovebaobao.servletconfig;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class PropertiesDemo {

    public static void main(String[] args) throws IOException {
        fun_Write();    //写入(也可以直接打开.properties文件,写入name=baozi)
        fun_Read();     //读取
    }

    public static void fun_Write() throws IOException{
        Properties pro=new Properties();
        FileWriter fw=new FileWriter("./src/db.properties");// ./当前路径指当前工程根目录即studytest文件(直接db.properties则保存在studytest目录下)
        pro.setProperty("name","baozi");
        pro.setProperty("age","21");
        pro.setProperty("email","[email protected]");
        pro.store(fw,"src/db.properties");      //第二个参数为注释信息
        fw.close();
    }

    public static void fun_Read() throws IOException{
        Properties pro=new Properties();
        FileReader fr=new FileReader("./src/db.properties");
        pro.load(fr);
        fr.close();
        System.out.println(pro);
    }

}

输出:
{age=21, name=baozi, [email protected]}
2、路径的获取以及用类加载器加载资源(用前面的Servlet1跳转TestPath)

package com.lovebaobao.servletconfig;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
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.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;

@WebServlet(name = "TestPath",
        urlPatterns = "/servlet/TestPath")

public class TestPath extends HttpServlet {

    private static final long serialVersionUID = 1L;

    ServletContext context = null;

    @Override
    public void init() {
        context = this.getServletContext();
        System.out.println("UserDir:" + System.getProperty("user.dir"));  //当前工作区目录

        //普通方式读取文件路径
        try {
            File file = new File("src/com/lovebaobao/servletconfig/db.properties");     //相对于项目根目录
            //获取定义路径(即定义时写的什么,获取的就是什么)
            System.out.println("\npath:" + file.getPath());
            //获取资源的绝对路径
            System.out.println("absolutePath:" + file.getAbsolutePath());
            System.out.println("RealPath:" + context.getRealPath("src/com/lovebaobao/servletconfig/db.properties"));
            System.out.println("");
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf-8");     //指定解析编码方式
        response.setCharacterEncoding("utf-8");     //指定编码方式

        context = this.getServletContext();

        //通过路径加载资源
        //使用class变量的getResourcesAsStream()
        Properties pro=new Properties();
        try{

            System.out.println(TestPath.class.getResource(""));     //当前类所在包的路径
            System.out.println(TestPath.class.getResource("/"));        //当前类的根目录

            InputStream in=TestPath.class.getResourceAsStream("db.properties");// src/db.properties

            pro.load(in);
            Enumeration em=pro.propertyNames();
            while(em.hasMoreElements()){
                String key=(String)em.nextElement();
                String value=pro.getProperty(key);
                response.getWriter().write(key+"="+value+"<br />");//\n转义符在页面解析为text/html的时候会被忽略
            }

            in=TestPath.class.getResourceAsStream("db.properties");
            //InputStream转换为String
            byte[] bytes=new byte[in.available()];
            int num=in.read(bytes);     //从输入流读取一定数量的字节,并将其存储在缓冲区bytes中,返回实际读取的字节数
            String content=new String(bytes);
            System.out.println(content);

            response.getWriter().print("***********使用class变量的getResourcesAsStream()************<br />");
            in.close();
        }catch (Exception e){
            e.printStackTrace();
        }

        //使用class.getClassLoader()所得到的java.lang.ClassLoader的getResourceAsStream()
        /*
        1、类加载器会把该资源文件和class文件等同一样加载到内存中,所以资源太大,内存容易爆;
        2、只要当类加载的时候这个文件才会被加载到内存中,现在假如我们修改了这个db.properties资源文件并保存,但是我们在读取的内
        容还是之前的内容,因为我们修改的是db.properties资源文件,而没有修改类文件,所以类加载器并不会再次加载类,那么这次修改是无效的
         */
        try{

            System.out.println(TestPath.class.getClassLoader().getResource(""));
            System.out.println(TestPath.class.getClassLoader().getResource("/"));       //均为当前类根目录

            InputStream in=TestPath.class.getClassLoader().getResourceAsStream("db.properties");   //com/lovebaobao/servletconfig/db.properties

            pro.load(in);
            Enumeration em=pro.propertyNames();
            while(em.hasMoreElements()){
                String key=(String)em.nextElement();
                String value=pro.getProperty(key);
                response.getWriter().write(key+"="+value+"<br />");
            }

            in=TestPath.class.getClassLoader().getResourceAsStream("db.properties");
            //InputStream转换为String
            byte[] bytes=new byte[in.available()];
            int num=in.read(bytes);     //从输入流读取一定数量的字节,并将其存储在缓冲区bytes中,返回实际读取的字节数
            String content=new String(bytes);
            System.out.println(content);

            response.getWriter().print("***使用class.getClassLoader()所得到的java.lang.ClassLoader的getResourceAsStream()***<br />");
            in.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        //response.setContentType("text/html;charset=utf-8");
        doPost(request,response);
    }
}

控制台输出:
123456
baozi
http://baidu.com
UserDir:D:\Program Files (x86)\MyEclipse 2017 CI\apache-tomcat-8.5.32\bin

path:src\com\lovebaobao\servletconfig\db.properties
absolutePath:D:\Program Files (x86)\MyEclipse 2017 CI\apache-tomcat-8.5.32\bin\src\com\lovebaobao\servletconfig\db.properties
RealPath:F:\IdeaProjects\studytest\out\artifacts\studytest_war_exploded\src\com\lovebaobao\servletconfig\db.properties

file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/com/lovebaobao/servletconfig/
file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/
#com.lovebaobao.servletconfig/db.properties
#Wed Aug 01 15:36:19 CST 2018
age=12
[email protected]
name=zibao

file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/
file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/
#src/db.properties
#Wed Aug 01 15:36:19 CST 2018
age=21
[email protected]
name=baozi

网页输出:
age=12
[email protected]
name=zibao
***********使用class变量的getResourcesAsStream()************
age=21
[email protected]
name=baozi
***使用class.getClassLoader()所得到的java.lang.ClassLoader的getResourceAsStream()***

总结(自己的理解):
JavaWeb的路径大类分为两种:
1、绝对路径
2、相对路径

  • 相对当前文件
  • 相对当前类目录(TestPath.class.getResourceAsStream()和TestPath.class.getClassLoader().getResourceAsStream()):

由控制台输出我们知道:
System.out.println(TestPath.class.getResource(""));   //不加斜杠
System.out.println(TestPath.class.getResource("/"));     //加斜杠
-->file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/com/lovebaobao/servletconfig/
-->file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/
System.out.println(TestPath.class.getClassLoader().getResource(""));
System.out.println(TestPath.class.getClassLoader().getResource("/")); 
-->file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/
-->file:/F:/IdeaProjects/studytest/out/artifacts/studytest_war_exploded/WEB-INF/classes/     

  • 相对当前工程(Web)文件

注意:设置response.setContentType("text/html;charset=utf-8");     //指定解析编码方式
           response.setCharacterEncoding("utf-8");     //指定编码方式
可以解决中文乱码,但是如果当前Servlet是另一个Servlet跳转过来的,不仅当前Servlet需要设置,跳转前的Servlet也需要设置(比如上面的TestPath是Servlet1跳转的,那么TestPath和Servlet就共有一个response),一般情况下:

  • 页面输出中文变乱码——setContentType设置无效
  • 页面输出中文变????——setCharacterEncoding设置无效

ClassLoader.getSystemResourceAsStream(String path),在Web容器中(即利用tomcat运行),无论如何都找不到资源;然而直接作为java文件运行,就可以正常运行,不知道怎么回事。。。

package com.lovebaobao.servletconfig;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class Test{

    InputStream in=ClassLoader.getSystemResourceAsStream("db.properties");
    Test(){
        try{
            byte[] bytes=new byte[in.available()];
            int count=in.read(bytes);
            String str=new String(bytes);
            System.out.println(str);
            System.out.print(ClassLoader.getSystemResource("")+"\n");
            System.out.print(ClassLoader.getSystemResource("/"));
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        Test t=new Test();
    }

}

输出:
#src/db.properties
#Wed Aug 01 15:36:19 CST 2018
age=21
[email protected]
name=baozi

file:/F:/IdeaProjects/studytest/out/production/studytest/
null

可见它的相对路径也与前面两种有很大差别呀

Servlet线程安全

当多个客户端并发访问同一个servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用servlet的service(),因此service方法如果访问了同一个资源的话,就有可能引发线程安全的问题,如果某个servlet实现了Single ThreadModel(标记接口),那么servlet引擎将以单线程模式来调用其service方法。对于实现了Single  ThreadModel接口的servlet,servlet引擎仍然支持对该servlet的多线程并发访问,其采用的方式是产生多个servlet实例对象,并发的每个线程分别条用一个独立的servlet实例对象。实现Single  ThreadModel接口并不能真正解决servlet的线程安全问题,因为servlet的引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个servlet实例对象被多个线程同时调用的问题,事实上,在servlet api2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。标准的解决方案是同步方式sychronized

线程安全问题的例子:

public class HelloWorldServlet extends HttpServlet{
    private String userName;    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        userName=request.getParameter("userName");
        PrintWriter out=response.getWriter();
        if(userName!=null&&userName!="")
        {
            out.print(userName);
        }
        else {
            out.println("用户名不存在");
        }
    }
}

现在有A,B个客户端同时请求HelloWorldServlet 这个实例,Servlet容器分配线程T1来服务A客户端的请求,T2来服务B客户端的请求,操作系统首先调用T1来运行,T1运行到第6行的时候得到了用户名为张三并保存,此时时间片段到了,操作系统开始调用T2运行也运行到第6行但是这个用户名是李四,此时时间片段又到了,操作系统又开始运行T1,从第7行开始运行,但是此时的用户名却成了李四,输出的时候确实李四(很明显是错误的),那么这个时候就出现了线程安全问题。

解决线程安全:
1、将全局变量改为局部变量(UserName的定义放入doGet函数中,因为每次调用这个方法的时候就会重写对userName的实例化,这样一来就不会存在线程安全问题了)
2、使用synchronized
protected synchronized  void doGet(HttpServletRequest request, HttpServletResponse response) 
如此T2必须要等T1执行完毕以后才可以执行,大大的影响了效率。
3、如果是静态资源则加上final表示这个资源不可以改变:
final static String url="jdbc:mysql://localhost:3306/blog";

HttpSession:
httpSession对象在用户会话期间存活的,不像ServletContext一样被所有的用户共享,所以说一个HttpSession在同一个时刻只用一个用户进行请求的,因此理论看来Session是线程安全的,其实并不是如此,因为同一个浏览器只能具有一个Session,那么这样一来就会出现Session线程安全问题

protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        String commandType=request.getParameter("commandType");
        HttpSession session=request.getSession();
        List list=(List)session.getAttribute("items");
        if ("add".equals(commandType)) {
            //添加
        }
        else if("delete".equals(commandType)){
            //删除
        }
        else {
            int count=list.size();
            for (int i = 0; i < count; i++) {
                //遍历
            }
        }
    }

上面是一个添加物品信息的一个简单伪代码,如果用户现在在一个浏览器窗口删除一件物品的同时又在另一个窗口去获取所有的物品这个时候就会出现线程安全;Servlet容器是多线程单实例的,这个时候Servlet容器就会分配2个线程来分别为删除物品和获取所有物品进行服务,如果其中一个线程刚好运行到14行时间片段结束,另一个线程这个时候又运行第10行删除一条物品信息,然后第一个线程又开始运行第15开始遍历,此时就会出现数组索性超出范围的错误。

HttpRequest:
httprequest是线程安全的,因为每个请求都会调用Service,都会创建一个新的HttpRequest对象和局部变量一样。

如果线程向静态list集合中加入了数据(a),数据用完后,一般要移除静态集合中的数据(a),否则集合中的数据越来越多,就会导致内存溢出。对象销毁了,静态资源的字节码仍然驻留在内存中。

猜你喜欢

转载自blog.csdn.net/lovedbaobao/article/details/81290370