javaWeb基础二:Servlet(java前后端交互的技术)

2. Servlet

2.1 定义

Servlet是sun公司提供的一门用于开发动态web资源的技术,可以实现和客户端的交互,接收客户端请求和给客户端返回响应

Sun公司在其API中提供了一个servlet接口,用户若想开发一个动态web资源需要完成以下2个步骤:

  1. 编写一个Java类,实现servlet接口。
  2. 把开发好的Java类部署到web服务器中。

2.2 Servlet接口

Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet

​ HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。

​ HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。

2.3 实现第一个Servlet

基础操作步骤:

  1. 编程工具上设置Tomcat配置。(有的是单独在项目中设置)

  2. 创建web项目。

    以2020版本的idea为例:(先创建基础的java项目,再在项目右键,点击Add Frameworks Support 添加,选择web项目。)

    注:不同的版本、编程软件也有不同的创建方式,有得可以直接创建web项目。

  3. 创建一个类,继承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目录下。

  1. 注册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>
  1. 编写前端页面。
<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>
  1. 在web.xml中设置进入项目默认访问的第一个文件。

    这里设置为上面的welcome.html

    <welcome-file-list>
        <welcome-file>welcome.htmlwelcome-file>
    welcome-file-list>
    

拓展:前端发送请求的三种方式:(前两种都只能以get方式提交,而第三种能够选择提交方式)

  1. 超链接(标签)
  2. window.location
  3. form表单(action,method(get/post)),使用submit按钮提交

解析过程:

​ Tomcat在加载Web应用时,就会把相应的web.xml文件中的数据读入到内存中。因此当Tomcat在解析web请求的时候,需要参考web.xml文件时,实际上只需要从内存中读取相关数据就可以了,不需要再到文件系统中读取web.xml。

图示:

javaWeb基础二:Servlet(java前后端交互的技术)_第1张图片

2.4 Servlet运行过程

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销毁");
    }
}

javaWeb基础二:Servlet(java前后端交互的技术)_第2张图片

2.4.1 Servlet生命周期

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() 每请求一次就会调用一次。

2.4.2 ServletConfig对象

在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);
}

2.4.3 使用@WebServlet注解

使用@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

2.5 线程安全问题

出现原因:多个客户端访问同一个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();//解锁
    }
}

2.6 重定向和转发

​ 在前端页面,想要跳转到其他的页面或者是Servlet,可以直接通过超链接、提交表单、location三种实现,但是单纯两个页面之间的的跳转,是没有经过后端任何数据处理和数据传递的,这样的实现在大多数情况下是没有意义的。所以在需要数据处理和页面之间的数据传递时,常常会经过Servlet来实现。

Servlet跳转到某个页面/Servlet的两种方式为:重定向和转发。

重定向是通过响应(response)来完成的,而转发是通过请求(request)完成。

2.6.1 重定向

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

    //在Servlet中使用 重定向方式 跳转到page.html
    response.sendRedirect("page.html");
}

2.6.2 转发

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

    //在Servlet中使用 转发方式 跳转到page.html
    request.getRequestDispatcher("page.html").forward(request,response);
}

注:请求转发时,需要将request和response一同转发。

2.6.3 重定向和转发的区别

浅显的说:

重定向是Servlet告诉浏览器,自己无法完成你的请求(第一次请求),并且通过响应告诉浏览器你想要完成这个请求应该去找谁,之后浏览器根据响应回来的信息(重定向的内容)重新给新的servlet或页面发送请求(第二次请求)。

转发则是Servlet知道自己无法完成浏览器的请求(一次请求),但是,我可以去找其他Servlet或页面帮你完成,而这些则不需要浏览器再操心。

(浏览器分别找重定向和转发借钱;

转发说:我虽然没有钱,但是我可以找别人借了,再借给你。

重定向说:我也没有钱,你找马云借吧,然后浏览器又去找马云借去了。(借了两次))

重定向 转发
前端发送请求的次数 2次 1次
跳转到普通页面(项目web文件夹下的页面) ok ok
跳转到外部页面的区别(如http://www.baidu.com) ok no
跳转到受保护页面的区别(WEB-INF里的页面) no ok

解释:

1.转发无法跳转到外部页面的原因:当前服务器不能直接访问外部服务器。

2.重定向无法跳转到受保护页面的原因:浏览器不能直接访问WEB-INF内的资源

2.7 中文乱码问题

出现原因:

前端后端编码不一致

浏览器默认使用UTF-8码表进行编码 ,Servlet使用ISO-8859-1码表进行编码

传输和接收方编码不一致导致乱码的产生

2.7.1 Request(请求)乱码

Request请求分为post和get,分别有不同的解决方案

2.7.1.1 POST请求

在Servlet的doPost方法中给请求的参数设置编码格式。

示例:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
    //先给请求中的参数设置编码格式
    request.setCharacterEncoding("UTF-8");
    //再通过getParameter获取数据。
		
}

注:

在执行 setCharacterEncoding()之前,不能执行任何 getParameter()操作。

通过 setCharacterEncoding 设置的编码方式只对 POST 方式提交的表单有效,对 GET 方式无效。

2.7.1.2 GET请求(Tomcat7.X版本)

解决方案一:将获取到的数据先采用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);
}

2.7.1.3 GET请求(Tomcat8.X版本)

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;
        }
    }
}

2.7.2 Response(响应)乱码

单独设置响应内容的编码格式

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
		
	//设置响应内容的编码格式
	response.setContentType("text/html;charset=UTF-8");
		
	response.getWriter().println("哈喽!");
}

2.7.3 跳转到中文页面路径乱码

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.不建议使用中文路径页面。

你可能感兴趣的:(javaWeb基础,java,tomcat,eclipse,servlet)