Servlet学习笔记

时间:2016-11-9 14:49

——JavaWeb三大组件

    Servlet        Listener        Filter

——JavaEE十三种核心技术

    JavaEE是一个开放的平台,它包括的技术很多,主要包括十三种核心的技术。 

    1、JDBC
        JDBC API是一个以统一的方式访问各种数据库,与ODBC类似,JDBC将开发者和私有数据库之间的问题隔离开来,由于它建立在Java语言之上,因此JDBC可以提供与平台无关的数据库访问。

        ODBC:
            开放数据库连接(Open Database Connectivity,ODBC)是微软公司开放服务结构(WOSA,Windows Open Services Architecture)中有关数据库的一个组成部分,它建立了一组规范,并提供了一组对数据库访问的标准API(应用程序编程接口)。这些API利用SQL来完成其大部分任务。ODBC本身也提供了对SQL语言的支持,用户可以直接将SQL语句送给ODBC。开放数据库互连(ODBC)是Microsoft提出的数据库访问接口标准。开放数据库互连定义了访问数据库API的一个规范,这些API独立于不同厂商的DBMS,也独立于具体的编程语言(但是Microsoft的ODBC文档是用C语言描述的,许多实际的ODBC驱动程序也是用C语言写的。)ODBC规范后来被X/OPEN和ISO/IEC采纳,作为SQL标准的一部分。

        JDBC定义了4种不同的驱动,具体来说包括:
            1)JDBC-ODBC桥
                在JDBC刚出现时,JDBC-ODBC桥是非常有用的,通过它,开发者可以使用JDBC来访问一个ODBC数据源。缺点是,它需要在客户机器上安装一个ODBC驱动,该驱动通常是运行在Windows操作系统的,使用这一类的驱动器,就失去了JDBC与平台无关的优势。此外,ODBC驱动器需要客户端的管理。 
            2)JDBC-native驱动桥
                JDBC-native驱动桥提供了一个建立在本地数据库驱动上的JDBC接口,没有使用ODBC。JDBC驱动将标准的JDBC调用转变为对数据库API的本地调用,使用该驱动也会失去JDBC跨平台的优势,并且需要安装客户端的本地代码。
            3)JDBC-network桥
                JDBC-network桥不需要客户端的数据库驱动,它们使用网络-服务器中层来访问一个数据库,这会引出诸如负载均衡、连接池等技术,数据缓冲也是可能的。由于该驱动通常可能带来相对较小的下载时间,并且它是跨平台的,不需要客户端的安装和管理,因此很适合用作Internet的应用程序。
            4)纯Java驱动
                该驱动使用纯Java数据库驱动来提供直接的数据库访问。由于该驱动运行在客户端,并且直接访问数据库,因此运行在这个模式上要使用一个两层的体系。要在一个N层的体系中使用该驱动,可以通过一个包含有数据访问代码的EJB,并且让该EJB为它的客户提供一个数据库无关的服务。

            桥:
                JDBC和ODBC,充当的就是桥的角色。

                现实生活中的桥是用来从河的一个岸到另一个岸的。而数据库和应用程序相当于河的两个岸,这两个岸通过JDBC或ODBC连接的,数据(存到数据库或从数据库中读取出来)就从这样的桥上通过,从一个岸到另一个岸。


        2、Java命名和目录接口(Java Naming and Directory Interface, JNDI)
                JNDI是Java Naming and Directory Interface的简写,意思是:Java命名及目录接口,它是为了对高级网络应用开发中使用的目录基础结构的访问。实际上这个目录是一个特殊的数据库,提供了对存储数据的快速访问,不像传统的目录服务访问方式,必须提供不同的API接口去访问不同的目录服务,如LDAP,NIS,ADS等,而它提供了一种标准的API来访问类型不同的目录。据说,使用完整的SDK可以开发哪些JNDI还不支持的目录服务提供者。

                JNDI是JavaEE的一个API,提供了一套标准的接口,以定位用户、机器、网络、对象以及服务。
                例如:可以使用JNDI来定位内部网络中的一台打印机,也可以使用它来定位Java对象或连接到一个数据库。
                JNDI可以用于EJB、RMI-IIOP、JDBC中。它是网络查找定位的标准方法。
                JNDI API被用来访问命名和目录服务,它提供了一个相容的模式来访问和操作企业范围大的资源,例如一个应用服务器中的DNS、LDAP、本地文件系统或者对象。

                在JDNI中,一个目录结构中的每一个节点被称为context,每一个JNDI的名字都是一个与context相对的,没有一个绝对名字的概念,一个应用可以使用InitialContext类来得到它的第一个context。
                    Context ctx = new InitialContext();
                通过这个初始的context,应用就可以经过目录树定位到需要的资源或者对象。例如,已经在WebLogic Server中配置了一个EJB,并且在myApp.myEJB中绑定了home接口,EJB的客户端在得到这样一个初始的context后,就可以使用以下的代码来定位到home接口了:
                    MyEJBHome home = ctx.lookup("myApp.myEJB");
                一旦得到所需对象的引用(在这个例子中,就是EJB的home接口),然后就可以调用它的方法了,为了一个在context中查找到一个对象,JNDI还提供了一个方法:
                    插入或者绑定一个对象到一个context中,在配置一个EJB时,这是非常有效的方法:
                        从context中移除一个对象。
                        列出context中的所有对象。
                        创建和删除subcontexts。

        3、企业 Java Beans(Enterprise Java Beans, EJB)
            随着Hibernate的兴起,EJB的地位受到了挑战。
            EJB3.0是一个轻量级的开发工具。
            JavaEE中一个引人注目的技术是EJB,它提供了一个架构来开发和配置到客户端的分布式商业逻辑,因此可以明显减少开发的扩展性、高度复杂企业应用的难度。EJB规范定义了EJB组件应该如何及何时与它们的容器交互。由容器来负责提供普通的服务,例如目录服务、事务管理、安全、资源池和容错。

            EJB规范定义了三类基本的bean:
                1)会话beans(session beans):
                    会话beans为业务流程建模,由于它们通常表示执行某个动作,因此可以把它们当做是动词,这个执行的动作可以是任何事情,例如增加数量,访问数据库,调用其它系统,调用其他企业的Bean。我们可以举出很多例子,包括一个计价引擎,一个工作流引擎,一个目录引擎,一个信用卡认证中心或者一个网上证券交易引擎。

                2)实体beans(Entity beans):
                    这是持久保存数据的代表,数据对象存储在数据库中,因此在服务器崩溃后数据仍然存在,多个客户端可以使用EJB来表示同样的数据。实体beans为企业数据建模,由于它们表示数据对象(就是缓存数据信息的Java对象),因此可以把它们当做名词。

                    实体beans的例子包括一种产品、一项订单、一张信用卡或一只股票。会话beans典型的方式是通过实体beans来实现业务目标的。例如一个证券交易引擎(会话beans)处理股票(实体beans)。

                3)Message-Driven beans:
                    消息bean为了支持异步开发模式而产生的。
                    Message-Driven beans也表示动作,这一点上它类似于会话beans,它们之间的不同点在于你只能够发送消息给Message-Driven beans的方式来调用它们。Message-Driven beans的例子包括了接收股票交易信息的beans,信用认证消息或工作流消息。这些Message-Driven beans也可以调用其他的企业beans。

                无状态和有状态:
                    无状态的beans(Stateless beans)是一个单一使用的服务,不维护任何的状态,在服务器崩溃时也会消失,而且生存周期也相对较短,例如一个无状态的 session beans 可能用作执行温度转换。

                    有状态的beans提供了一个传统的与客户端交互的方法,存储客户端的状态。在线购物车就是这样一个有状态的session beans 的典型例子。有状态的session beans在服务器崩溃时也会消失,而且生命周期也相对较短,并且每个实例只可以用在一个单一的线程中。

        4、Java Server Pages(JSP)
            或许你已经对微软的Active Server Pages(ASP)非常熟悉,JSP也是类似的技术,不过JSP是跨平台的,他们都是来帮助web开发者使用相对较少的代码就可以开发动态网页。即时web设计者不懂得编程,也可以使用JSP来开发动态的网页。JavaServerPages是HTML代码和Java代码的混合,在客户请求页面的时候,服务器就会处理Java代码,然后返回HTML页面给浏览器。

            你可能也听过JHTML,它是一个旧的标准,现在已经被JSP取代了。
            WebLogic Server不但支持JSP,还支持JHTML,不过,在默认设置下,WebLogic Server是不支持JSP的(对于5.1版本),必须编辑weblogic.properties来激活web服务器,对于JSPServlet来说,也是这样。

        5、Servlet
            Servlet是JSP的基础。
            Java --> Servlet --> JSP
            Servlet以Java为基础,JSP以Servlet为基础。
            Java本身并不支持Web开发,Servlet的出现使得Java能够对Web进行开发,又因为Servlet做界面比较困难,所以出现了JSP。

            Servlet提供的功能大部分与JSP相同,不同的是:JSP中大部分是HTML代码,其中只有少量的Java代码,而Servlet则相反,它完全使用Java代码编写,并且生成HTML代码。

            Servlet是一个在服务器上运行的Java程序,它可以扩展Web服务器的功能。这些服务器端的应用可以在被请求时动态执行,与传统Web服务器上的CGI、Perl脚本差不多,CGI脚本和Servlet的一个主要区别是:CGI脚本对于每次请求都启动一个全新的进程,需要额外的系统开销;而Servlet的执行只要在Servlet引擎内启动一个独立的线程就可以了,因此Servlet的扩展性也更好。

            在开发Servlet时,通常都要扩展javax.servlet.http.HttpServlet类,并且覆盖它的一些方法,主要包括:
                servlce():作为command-specific方法的一个调度程序。
                doGet():处理一个来自客户的HTTP GET请求。
                doPost():处理一个来自客户的HTTP POST请求。
                还有一些其他的方法来处理不同的HTTP请求——可以参考HttpServlet API的文档来得到更多相关的信息。

        6、Java IDL / CORBA
            通过Java的IDL支持,开发者可以将Java与CORBA集成。它们可以创建能配置在一个CORBA ORB中的Java对象,也可创建作为配置在其他ORB内的CORBA对象客户端的Java类。对于通过Java将你的新应用和以前的系统集成,后者提供了一个另外的方法。

        7、Java事务体系(JTA) / Java事务服务(JTS)
            JTA定义了一个标准的API,应用可以通过它来访问事务监控器。
            JTS是CORBA OTS事务监控器的一个基本实现。JTS指定了一个事务管理器的实现(Transaction Manager),这个管理器在一个高级别上支持Java事务API(JTA)规范,并且在一个低级别上实现了OMG OTS规范的Java映射。一个JTS事务管理器为应用服务器、资源管理器、Standalone应用和通信资源管理器提供事务服务。
            
        8、JavaMail和JavaBeans激活架构(JavaBeans ACtivation Framework, JAF)
            JavaMail是一个用来访问邮件服务器的API。JavaMail API提供了一套抽象类来模型化一个邮件系统,支持SMTP和IMAP服务器。
            JavaMail通过使用JavaBeans Activation Framework(JAF)来处理MIME加密的邮件附件。MIME字节楼和Java对象间可以互相转化。大多数的应用无需直接使用JAF。

        9、Java信使服务(Java Messaging Service, JMS)
            JMS往往配合EJB中的消息bean使用。
            JMS是一个用来和面向信息的中层通信的API,它不但支持点对点的域,也支持发布 / 订阅域,并且提供对担保信息传送、事务信息传送、持久信息和durable subscribers的支持,对于将应用和以前的backend系统集成,JMS提供了另外一个方法。

        10、扩展标记语言(Extensible Markup Language,XML)
            XML是一个用来定义其他标记语言的语言,它可被用于商业之间的数据共享。XML的发展是与Java分开的;不过,它的目标和Java类似,都是为了解决跨平台性,通过将Java与XML结合,可以得到一个完全跨平台的解决方案。多个公司都为了在Java和XML之间开发一个紧密的集成而工作,具体的信息,可以浏览Sun公司网站的Java-XML部分。

——JavaWeb开发体系

    中级开发:
        Java(基础语言)
        JDBC(数据库编程)
        MySQL(数据库)
        HTML    CSS    JavaScript(静态Web开发)
        XML
        Servlet    JSP(动态Web开发)

    高级开发:
        jQuery
        Ajax
        Struts2
        Spring
        Hibernate


——Tomcat和Servlet在网络中的位置

    图片 
 
——Servlet概述
一、什么是Servlet
    Servlet是JavaWeb的三大组件之一,它属于动态资源,Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
        接受请求数据
        处理请求数据
        完成响应
    例如客户端发出登录请求,这些请求都应该由Servlet来完成处理,Servlet需要我们自己来编写,每个Servlet必须实现javax.servlet.Servlet接口。

二、实现Servlet的方式
    实现Servlet有三种方式:
        1、实现javax.servlet.Servlet接口
        2、继承javax.servlet.GenericServlet类
        3、继承javax.servlet.http.HttpServlet类
    通常我们会去继承HttpServlet类来完成我们的Servlet,但学习还要从javax.servlet.Servlet接口开始学习。
    GenericServlet实现Servlet接口,HttpServlet继承GenericServlet抽象类。

    Servlet接口源代码:
    public interface Servlet
    {
        //该方法用于初始化Servlet,也就是把该Servlet装载到内存中。
        //该方法只会被调用一次。
        public void init(ServletConfig config) throws ServletException;

        //得到ServletConfig对象
        public ServletConfig getServletConfig();

        //该方法是服务方法,我们的业务逻辑代码写在该方法内
        //每次发起服务请求,都会调用该方法
        publi void service(ServletRequest request,ServletResponse response) throws ServletException,IOException;

        //调用该方法时得到Servlet的配置信息
        public String getServletInfo();

        //销毁该Servlet,从内存中清除
        //该方法只被调用一次
        public void destroy();
    }

三、配置XML
    1、如何让浏览器访问Servlet
        1)给Servlet指定一个Servlet路径(让Servlet与一个路径绑定在一起)
            <url-pattern>
        2)浏览器访问Servlet路径。

    2、如何给Servlet配置Servlet路径
        web.xml
        <servlet>
            <servlet-name>XXX</servlet-name>    //name可以随便写,只要上下对应即可。
            <servlet-class>com.wyc.servlet.XXServlet</servlet-class>    //不能加.java扩展名,不能加斜线,因为该路径会被ClassLoader调用,ClassLoader的路径不能加斜线。
        </servlet>

        <servlet-mapping>
            <servlet-name>XXX</servlet-name>
            <url-pattern>/aa/bb/XXServlet</url-pattern>    //路径可以随便写,并且必须写 / 
                                                                                        //当访问http://localhost:8080/HelloWeb/aa/bb/XXServlet时,
                                                                                        //就会打开对应Servlet了。
        </servlet-mapping>

        <servlet>
            <!--servlet-name给该Servlet取名,名字可以自己定义,默认就使用该Servlet的名字--> 
            <servlet-name>information</servlet-name>
            <!--servlet-class要指明该Servlet放在哪个包下,形式是包/包/...类/    (无.java)--> 
            <servlet-class>com.wyc.servlet.information</servlet-class>
        </servlet>
        <!--Servlet的映射-->
        <servlet-mapping>
            <!--两个servlet-name的名称要相同--> 
            <servlet-name>information</servlet-name>
            <!--Web服务器先看url-pattern的名称,也就是访问该Servlet的资源名部分--> 
            <url-pattern>/information</url-pattern>
        </servlet-mapping>

        分析可能出现的错误
            ①<servlet-name>hahahaha</servlet-name>,名字不一样,会在启动Tomcat的时候报错。
            ②<servlet-class>com.wyc.hahaha</servlet>,如果加上.java扩展名,会出现500错误。
            ③写错资源名:http://localhost:8080/wycweb/资源名  也就是<url-pattern>,会出现404错误。

    3、Servlet特性:
        Servlet是单例的,一个类只有一个对象。
        是线程不安全的,效率高。

        Servlet类由我们来写,但对象由服务器来创建,并且由服务器通过getMethod()方法来调用对应的方法(doGet()  doPost())。

        javax.servlet接口:
            定义所有servlet都必须实现的方法。
            此接口定义了初始化servlet的方法,为请求提供服务的方法和从服务器移除servlet的方法。
            这些方法称为生命周期方法,它们是按照以下顺序调用的:
                1、构造servlet,然后使用init方法将其初始化。
                2、处理来自客户端的对service方法的所有调用。
                3、从服务中移除servlet,然后使用destory方法销毁它,最后进行垃圾回收并终止它。

            除生命周期方法外,此接口还提供了getServletConfig方法和getServletInfo方法,Servlet可以使用getServletConfig()方法获得任何配置信息,而getServletInfo()方法允许Servlet返回有关其自身的基本信息,比如作者、版本等。

            方法摘要:
                public void destory()
                    由Servlet容器调用,指示将从服务器中移除该servlet。
                    此方法仅在Servlet的service方法已经退出或者在超过了时期之后调用一次,在此方法调用之后,Servlet容器不会再对此Servlet调用service方法。此方法为Servlet提供了一个清除持有的所有资源(比如内存、文件句柄和线程)的机制,并确保任何持久状态都与内存中该servlet的当前状态保持同步。
                    该方法不会销毁Servlet,而是释放所有资源,然后通过JVM的垃圾回收机制来销毁Servlet。

                public void init(ServletConfig config) throw ServletException
                    由servlet容器调用,指示将该Servlet放入服务。
                    Servlet容器仅在实例化Servlet之后调用init方法一次。
                    在Servlet可以接收任何请求之前,init方法必须成功完成,否则无对象,无法完成请求响应。
                        出现以下情况时,Servlet容器无法将Servlet放入服务:
                            1)抛出ServletException。
                            2)未在Web服务器定义的时间段内返回。(超时)
                        config:包含Servlet的配置和初始化参数的ServletConfig对象。

                public void service(ServletRequest req,ServletResponse res) throws ServletException,java.io.IOException
                    由Servlet容器调用,以允许Servlet响应某个请求。
                    此方法仅在servlet的init()方法成功完成后调用。(也就是说,首先要保证内存中存在Servlet对象)
                    应该为抛出或发送错误的Servlet设置相应的状态代码。
                    Servlet通常运行在可同时处理多个请求的多线程Servlet容器中,开发人员必须知道要同步对所有共享资源(比如文件、网络连接以及servlet的类和实例变量)的访问。
                        
                    req:包含客户端请求的ServletRequest对象。
                    res:包含Servlet的响应的ServletResponse对象。

                public ServletConfig getServletConfig()
                    该方法会在init()方法之后被调用。
                   返回ServletConfiguration对象,该对象包含此Servlet的配置信息和启动参数,返回的ServletConfig对象是传递给init方法的对象。此接口的实现负责存储ServletConfig对象,以便此方法可以返回对象,实现此接口的GenericServlet类已经这样做了。
                    return:初始化此Servlet的ServletConfig对象。

                public String getServletInfo()
                    返回有关Servlet的信息,比如作者、版本等。
                    此方法返回的字符串应该是纯文本,不应该是任何种类的标签(比如HTML、XML等)。
                    return:包含servlet的信息。

                生命周期方法全部由Servlet容器进行调用。

四、Servlet生命周期
    1、发送http请求。
    2、服务器解析主机。
    3、服务器解析Web应用。
    4、服务器解析资源名(HTML文件)。
    5、定位Servlet文件所在包。
    6、在定位到.servlet文件所在包后,使用反射机制,创建实例。
    7、init()方法将该实例装载到内存(该方法只调用一次)。实际上Servlet就是单例。
    8、Web服务器把接收到的请求封装成Request对象,作为service方法的参数传入service方法,该方法会被调用多次,每访问一次(也就是每发送一次请求)Servlet,它的service就会被调用一次。 
    9、在service方法中获取response对象(该对象有各种信息)。
    10、分解response对象信息,形成http响应格式信息。
    11、返回请求。 
    12、在某些情况下(三种),Web服务器回去调用该Servlet的destroy方法,将该Servlet销毁。
        关闭服务器,重新加载Web应用,关闭电脑。 
 
    请简述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()方法销毁该Servlet。
                以下情况会导致调用destroy()方法:重启Tomcat、reload该Web应用、重启电脑。

——ServletConfig接口
 
    ServletConfig中包含的是Servlet的配置信息。
    当Servlet执行时,对应的web.xml中的信息会被加载到内存。
    也就是说,ServletConfig对象对应的是Servlet的web.xml文件信息。

    方法摘要:
        String getServletName()
            返回的就是<servlet-name>XXX</servlet-name>的值。
            并不提供获取Servlet类的方法,因为JDK认为这样是不安全的。

        ServletContext getServletContext()
            获取Servlet上下文对象,可以理解为当前项目对象。
 
        String getInitParameter(String name)
            通过名称获取指定初始化参数的值。
 
        Enumeration getInitParameterNames()
            获取所有初始化参数的名称。

    因为ServletConfig中的信息都来自于web.xml,所以可以直接获取xml文件中的信息。
 
        <servlet>
            <servlet-name>XXServlet</servlet-name>
            <servlet-class>com.wyc.servlet.XXServlet</servlet-class>

            <init-param>
                <param-name>p1</param-name>
                <param-value>v1</param-value>
            </init-param>
            <init-param>
                <param-name>p2</param-name>
                <param-value>v2</param-value>
            </init-param>
        </servlet>
        如果想要获取v1的值,可以使用getInitParameter(String name)来获取。
        如果想要获取v1和v2的值,可以使用getInitParameterNames()来获取所有参数的名称。

        获取初始化参数:
        public void init(ServletConfig servletconfig) throws ServletException {
            System.out.println("init...");
//         System.out.println(servletconfig.getInitParameter("p1"));
//         System.out.println(servletconfig.getInitParameter("p2"));
            Enumeration<String> es = servletconfig.getInitParameterNames();
            while(es.hasMoreElements())
            {
                System.out.println(servletconfig.getInitParameter(es.nextElement()));
            }
        }

——GenericServlet概述(抽象类)

    这个类的存在使得编写Servlet更加方便。它提供了一个简单的方案,这个方案用来执行有关Servlet生命周期的方法以及在初始化时对ServletConfig对象和ServletContext对象进行说明。

    GenericServlet是Servlet接口的实现类,我们可以通过继承GenericServlet来编写自己的Servlet。

    该类提供了一个无参init()方法,用来执行子类特有的一些代码,不能覆盖有参init(ServletConfig servletConfig)方法,因为有参init方法中有如下代码:this.config = servletConfig;

    无参init()方法会由有参init()方法调用。

——HttpServlet

    方法摘要:
        doGet(HttpServletRequest request,HttpServletResponse response)
            接受get请求。
        doPost(HttpServletRequest request HttpServletResponse response)
            接受post请求。
        void service(ServletRequest request, ServletResponse response)
            这是一个生命周期方法,当接收到请求后,首先会调用该方法。
            将两个参数强转为两个支持http相关协议的参数类型。
            再调用本类的service(HttpServletRequest request,HttpServletResponse response)方法。
        protected service(HttpServletRequest request,HttpServletResponse response)
            接收强转后的支持http协议的参数。
            它会通过request.getMethod()得到当前请求的请求方式,例如get请求或post请求。
            再根据请求方式调用doGet()或者doPost()
            doGet()和doPost()方法需要我们自己重写。

图片

    ServletConfig源代码
    public class ServletConfig implements Servlet
    {
        private ServletConfig config;
 
        public void destroy(){}
 

        //该方法会在init()方法之后被调用
        //在init()方法被调用后,本类的成员this.config已经有值了。
        public ServletConfig getServletConfig()
        {
            return this.config;
        }

        public String getServletInfo()
        {
            return null;
        }

        public void init(ServletConfig config) throws ServletException
        {
            //将Tomcat传递的ServletConfig赋值给本类的一个成员,方便在其它方法中调用。
            this.config = config;
        }

        //每次处理请求都会被调用。
        public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException
        {
            //这里是否可以使用ServletConfig的对象呢?
            //可以,因为在init()之后会调用该方法。
        }
    }


——通过继承HttpServlet开发Servlet

    1、通过继承HttpServlet开发Servlet需要重写doGet()和doPost()方法。
    2、表单提交数据get请求和post请求的区别:
        1)从安全性看get请求的安全性小于post的安全性,get请求提交的数据会在浏览器的地址栏显示。
        2)从提交的内容大小看,get请求提交的内容数量小于post请求,get提交的数据不能大于2KB,而post提交的数据理论上不受限制,但是实际开发中建议不要大于64KB。
        3)从请求响应速度来看,get请求响应速度大于post请求,get要求服务器立即处理请求,而post请求可能形成一个请求队列。



——在MyEclipse中开发Servlet
 
    MyEclipse可以开发JSP、Servlet、Struts2、Hibernate、Spring
    一、在MyEclipse中新建一个Web Project工程,MyEclipse会自动创建下图所示目录结构:
                图片
        HaHa:
            Web工程的名称,该工程部署时,在webapps目录下就会创建一个HaHa的Web应用。
        src:
            Java程序的开发目录,该目录下编写的所有程序在部署时,会自动部署到HaHa/WEB-INF/classes目录下。
        WebRoot:
            WebRoot对应Web应用的根目录,该目录下的所有子目录和子文件在部署时,会原封不动的发布到Web应用目录下。

        开发步骤:
            1、建立一个Web工程。
            2、在src目录下建立一个包。
            3、右键单击包创建一个Servlet文件。
                选中doGet()和doPost(),然后点击Next。
                选中:Generate/Map web.xml file(创建XML配置文件),单击Finish。
            4、配置Tomcat
            5、发布(部署):实际上就是将Web资源拷贝到webapps中。 

    MyEclipse基本配置:
        1、修改MyEclipse运行环境
            window——preferences——Java——Installed JREs——removeJDK——Add——Standard VM
            ——Directory找到JDK目录、确认——选中JDK、确认
 
        2、修改项目JRE
            右键项目——Build Path——Configur Build Path——Libraries——remove JRE System—
            —Add Libraries(多个jar包集合到一起就是一个Libraries类库)——选中 JRE System 
            Libraries——选中 Workspace Default JDK、确认
 
        3、配置Tomcat
            window——preferences——MyEclipse——Servers——Tomcat——选择安装版本——选中
            Enable——选择Tomcat安装目录——点击Apply——确认
 
        4、设置编码格式
            window——General——Workspace——选择utf-8——点击Apply——确认
 
        5、设置文件默认编码
            window——MyEclipse——Files and Editors——选择要修改的文件

        6、window——Preferences——General——Editors——File Association——JSP——MyEclipse JSP Editor(设置为default)

        7、搜索jsp,将编码设置为UTF-8

        8、右键Tomcat,选择Configure Server Connector,选择对应服务器的Launch,选择Run mode。
 
        9、删除MyEclipse Derby
            General——Startup and Shutdown——MyEclipse Derby——重启MyEclipse

        10、如果是Windows7,还需要修改字体:
            C:WindowsFonts,找到Courier New,鼠标右键——显示
            Ceneral——Appearance——Colors and Fonts——Basic——Text Font——Edit——选择Courier New字体 

——Servlet接口实现类

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

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

    三、HttpServlet在实现Servlet接口时,复写了service方法,该方法体内的代码会自动判断用户的请求方式,调用对应的doGet
        或doPost方法,因此开发人员在编写Servlet时,通常只需要复写doGet或doPost方法,而不需要复写service方法。


——Servlet的一些细节

一、Servlet与线程安全
    因为一个类型的Servlet只有一个实例对象,那么就有可能会出现一个Servlet同时处理多个请求的情况,那么Servlet是否为线程安全的呢?答案是:不是线程安全的。这说明Servlet的工作效率很高,但是同时也存在线程安全问题。

    所以我们不应该在Servlet中轻易创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,而另一个线程对这个成员变量进行读操作。

    解决方法:
        1、不要在Servlet中创建成员,创建局部变量即可,这样每个线程独有一份数据,互不影响。
        2、可以创建无状态成员,例如仅仅用来输出或者是调用一下的方法等。
        3、可以创建有状态的成员,但是状态必须为只读的,例如只提供get方法,不提供set方法。 

    因为Servlet是单例,因此会出现线程安全问题,比如:
        售票系统,如果不加同步机制,则会出现问题。

    同步原则:
        1)如果一个变量需要多个用户共享(例如网站访问量或卖票等),则应当在访问该变量的时候加上同步代码块:
                synchronized(this){ }
        2)如果一个变量不需要共享,则直接在doGet()或者doPost()方法中定义,这样不会存在线程安全问题,因为是局部变量。

二、让服务器在启动时就创建Servlet

    默认情况下,服务器会在某个Servlet第一次收到请求时创建它,也可以在web.xml文件中对Servlet进行配置,使服务器启动时就创建Servlet。

    因为Servlet在第一次访问时会比较慢,所以可以在服务器启动时就完成Servlet的初始化。

    需求:当网站启动的时候,可能会要求初始化一些数据(比如创建某些数据库临时表),或者完成一些定时任务(比如定时写日志、备份数据、发送邮件等)。

    解决方法:可以通过配置<load-on-startup>和多线程来解决。
        <load-on-startup>:通过配置<load-on-startup>,我们可以指定某个Servlet自动创建。

    模拟一个定时发送邮件的功能:
        <servlet>
            <servlet-name>MyInitServlet01</servlet-name>
            <servlet-class>com.wyc.servlet.MyInitServlet01</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    设置该Servlet随着服务器的启动而加载,加载顺序为第一位,可以在该Servlet中开启一个线程,专门用于监控发送邮件。 

    参数从0开始,也可以不从0开始,执行顺序是按照从小到大执行。
    当参数为-1或者不写时,默认不加载。

    代码如下:
        <servlet>
            <servlet-name>XXServlet01</servlet-name>
            <servlet-class>com.wyc.servlet.XXServlet01</servlet-class>
            <load-on-startup>0</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>XXServlet01</servlet-name>
            <url-pattern>/XXServlet01</url-pattern>
        </servlet-mapping>

        <servlet>
            <servlet-name>XXServlet02</servlet-name>
            <servlet-class>com.wyc.servlet.XXServlet02</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>XXServlet02</servlet-name>
            <url-pattern>/XXServlet02</url-pattern>
        </servlet-mapping>

        <servlet>
            <servlet-name>XXServlet03</servlet-name>
            <servlet-class>com.wyc.servlet.XXServlet03</servlet-class>
            <load-on-startup>2</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>XXServlet03</servlet-name>
            <url-pattern>/XXServlet03</url-pattern>
        </servlet-mapping> 

三、<url-pattern>
    <utl-pattern>是<servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL,它必须是以"/"开头。

    1、由于客户端是通过URL地址访问Web服务器中的资源,所以Servlet程序若想被外界访问,必须把Servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>标签和<servlet-mapping>标签完成。

    2、<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称(Servlet的文件名)和Servlet的完整类名(包名+类名)。

        一个已经注册的Servlet可以被多次映射,也就是说可以与多个url-pattern关联,代码如下:
        <servlet>
            <servlet-name>LoginServlet</servlet-name>
            <servlet-class>com.wyc.servlet.LoginServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>LoginServlet</servlet-name>
            <url-pattern>/LoginServlet</url-pattern>
        </servlet-mapping> 

        也可以写成:
        <servlet-mapping>
            <servlet-name>LoginServlet</servlet-name>
            <url-pattern>/HaHaHa</url-pattern>(HaHaHa.html也可以)
        </servlet-mapping> 
        此时在地址栏中输入:http://localhost:8080/HaHaHa也可以访问LoginServlet了。
        当映射一个Servlet的时候,可以映射多层,比如:
            <url-pattern>/servlet/index.html</url-pattern>
            还可以看出,后缀名是html的文件不一定就是HTML,可能是假象。

    3、还可以在<url-pattern>中使用通配符,所谓通配符就是星号" * ",星号可以匹配任意URL前缀或者后缀,使用通配符可
        以命名一个Servlet绑定一组URL,例如:
            <url-pattern>/servlet/*</url-pattern>:/servlet/a 、 /servlet/b,都匹配/servlet/*(匹配路径)
            <url-pattern>*.do</url-pattern>:/abc/def/ghi.do、/a.do,都匹配*.do(匹配扩展名)
            <url-pattern>/*<url-pattern>:匹配所有URL。
        需要注意得是,通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符,例如:/*.do就是错误的,因为*出现在URL中间位置上,*.*也不对,因为一个URL中最多只能出现一个通配符。

        注意,通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>,那么访问路径会去匹配更具体的<url-pattern>

        通配符优先级:谁匹配的越多(*.do),谁的优先级越高,谁匹配的越少(/*),谁的优先级越低。
            “ / ”:什么都能匹配,所以它的优先级最低。 

    4、同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子标签的设置值可以是同一个servlet的注册名。

    5、在Servlet映射到URL中的时候也可以使用通配符(* ),但是只能有两种固定的格式,一种格式是“ * . 扩展名 ”:
            <url-pattern>/*</url-pattern>  或  <url-pattern>*.html</url-pattern>
        另一种格式是以斜线(/)开头,以(/*)结尾。代码如下
            /*        /news/* 

    6、对于如下的一些映射关系:
        Servlet1 映射到 /abc/*
        Servlet2 映射到 /*
        Servlet3 映射到 /abc
        Servlet4 映射到 *.do
        问题:
            判断原则:
                谁的匹配度高,谁就被调用。
                *.do的优先级最低。

        Servlet引擎就是Web服务器。
        当请求URL为 " /abc/a.html "," /abc/* " 和 " /* " 都匹配,哪个Servlet响应?
            Servlet引擎将调用Servlet1。
        当请求URL为 " /abc " 时," /abc/* " 和 " /abc " 都匹配,哪个Servlet响应?
            Servlet引擎将调用Servlet3。
        当请求URL为 " /abc/a.do " 时," /abc/* " 和 " *.do "都匹配,哪个Servlet响应?
            Servlet引擎将调用Servlet1。
        当请求URL为 " a.do " 时," /* " 和 " *.do " 都匹配,哪个Servlet响应?
            Servlet引擎将调用Servlet2。
        当请求URL为 " /xxx/yyy/a.do " 时," /* " 和 " *.do " 都匹配,哪个Servlet响应?
            Servlet引擎将调用Servlet2。


四、web.xml文件的“继承”
    CATALINA_HOMEconfweb.xml文件中的内容,相当于写到了每个项目的web.xml文件中,它是所有web.xml文件的父文件。

    <servlet-name>default</servlet-name>
    它的优先级最低,如果一个请求没有Servlet来处理,那么它来处理,它显示404。
    也就是说,当访问路径不存在时,会执行该Servlet。

    在web.xml文件中有全部的MIME类型,可以通过扩展名来查询。

——Servlet与反射

    当服务器获取到URL时,会截取Servlet路径与<url-pattern>进行匹配,如果匹配成功,找到对应的<servlet-class>,可以通过
        Class c = Class.forName("完整类名");
    来获取当前Servlet类的对象,然后再通过:
        Method method = c.getMethod("service",ServletRequest.class,ServletResponse.class);
    来获取到一个method方法对象,然后可以通过method.invoke()来完成对其调用。

    通常情况下,只要和配置相关,就有反射机制。


——ServletContext

    一个项目只有一个ServletContext对象。
    可以在多个Servlet中获取这个唯一的对象,可以使用它来进行多个Servlet之间的数据传递。

一、ServletContext概述
    服务器会为每个应用创建一个ServletContext对象。
        1)ServletContext对象的创建是在服务器启动时完成的。
        2)ServletContext对象的销毁是在服务器关闭时完成的。
    也就是说ServletContext生命周期最长。

    ServletContext对象的作用是在整个Web应用的动态资源之间共享数据,例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值了,这就是共享数据。

二、获取ServletContext
    1、在ServletConfig中获取ServletContext对象:
            在void init(ServletConfig config)中:
                ServletContext context = config.getServletContext();
                    ServletConfig类的getServletContext()方法可以用来获取ServletContext对象。
    2、在GenericServlet或HttpServlet中获取ServletContext对象:
            GenericServlet类实现了ServletConfig接口,所以也具备getServletContext方法,所以可以直接使用this.getServletContext()来获取ServletContext对象。
    3、在HttpSession中获取ServletContext对象:
            ServletContext getServletContext();
    4、在ServletContextEvent中获取ServletContext对象:
            ServletContext getServletContext();

三、域对象的功能
    域对象就是用来在多个Servlet中传递数据的,所以说所有的域对象都有存、取数据的功能。
    ServletContext是JavaWeb的四大域对象之一:
        PageContext
        ServletRequest
        HttpSession
        ServletContext

    所有的域对象都有存取数据的功能,因为域对象内部都有一个Map,用来存储数据,下面是ServletContext对象用来操作数据的方法:
        void setAttribute(String name,Object value)
            用来存储一个对象,也可以称之为存储一个域属性。
            需要注意的是,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的value,这一特性与Map集合相同。

        Object getAttribute(String name)
            通过name来获取ServletContext中的value,在获取之前需要先存储。
            因为存入的是Object,所以取出时需要进行强制类型转换。

        void removeAttribute(String name)
            用来移除ServletContext中的域属性,如果参数name不存在,那么该方法什么都不做。

        Enumeration getAttributeNames()
            获取所有域属性的名称。

四、获取应用初始化参数
    可以使用ServletContext来获取在web.xml文件中配置的应用初始化参数。
    注意:应用初始化参数与Servlet初始化参数不同。

    Servlet也可以获取初始化参数,但它是局部的参数,也就是说,一个Servlet只能获取自己的初始化参数,不能获取别人的,即初始化参数只为一个Servlet准备。
        
    可以配置公共的初始化参数,也就是整个Web应用的参数,为所有Servlet而使用,这需要使用ServletContext才能获取。

    <web-app ...>
        <context-param>
            <param-name>paramName01</param-name>
            <prarm-value>paramValue01</param-value>
        </context-param>
        <context-param>
            <param-name>paramName02</param-name>
            <param-value>paramValue02</param-value>
        </context-param>
    </web-app>

    获取参数方法:
        当前Servlet获取value方法:this.getInitParameter("name");
        ServletContext获取value方法:getServletContext().getInitParameter("name");

    当指定name的value不存在时,返回null。

五、获取资源的相关方法
    1、获取真实路径
        可以使用ServletContext对象来获取Web应用下的资源,例如在HelloWeb应用的根目录下创建a.txt文件,现在想
        在Servlet中获取这个资源,就可以使用ServletContext来获取。
            1)获取a.txt的真实路径:String realPath = ServletContext.getRealPath("/a.txt");
                realPath的值为:F: omcat7webappsHelloWeba.txt
            2)获取b.txt的真实路径:String realPath = ServletContext.getRealPath("/WEB-INF/b.txt");
                realPath的值为:F: omcat7webappsHelloWebWEB-INF/b.txt

        getRealPath()方法可以获得带盘符的真实路径。
        默认会获取WebRoot目录下的文件路径。 

    2、获取资源流
        不止可以获取资源的路径,还可以通过ServletContext获取资源流,即把资源以输入流的方式获取:
            获取a.txt资源流:InputStream in = ServletContext.getResourceAsStream("/a.txt");
            获取b.txt资源流:InputStream in = ServletContext.getResourceAsStream("/WEB-INF/b.txt");
        也可以通过realPath自己创建流。

    3、获取指定目录下所有资源路径
        还可以使用ServletContext获取指定目录下所有资源路径,例如获取/WEB-INF目录下所有资源的路径:
            Set st = context.getResourcePaths("/WEB-INF");
            System.out.println(st);
            输出结果为:[/WEB-INF/lib/ , /WEB-INF/classes/ , /WEB-INF/b.txt , /WEB-INF/web.xml ]
        注意:本方法必须以“ / ”开头。
        只获得子目录,不获取孙目录。

六、访问量统计
    当访问一个网站的时候,无论是哪个用户访问指定页面,都会累加访问量,所以这个访问量统计应该是整个项目共享的,很明显,这需要使用ServletContext来保存访问量。

    逻辑分析:
        最初访问页面时ServletContext中没有保存访问相关的属性。
        当第一次被访问时,创建一个变量,设置其值为1,并保存到ServletContext中。
        当以后访问时,就可以从ServletContext中获取这个变量,然后在其基础之上+1。

        ServletContext application = this.getServletContext();    //获取ServletContext对象
        Integer count = (Integer)application.getAttribute("count");    //获取ServletContext对象中的count属性
        if(count == null)
            count = 1;    //如果ServletContext中不存在count属性,那么count的值为1,表示第一次访问。
        else
            count++;    //如果ServletContext中存在count属性,说明以前被访问过,那么让count的值在原来基础上+1。
        response.serContentType("text/htmllcharset=utf-8");
        response.getWriter().print(count);    //向客户端响应本页面被访问的次数。
        application.setAttribute("count",count);    //将count的值保存到ServletContext对象中。

    只要服务器不关闭,则ServletContext一直存在。

七、获取类路径下的资源
    Class
    ClassLoader
    这里所指的类路径下的资源,对于JavaWeb应用而言,就是获取classes目录下的资源:
        /WEB-INF/classes和/WEB-INF/lib/下的jar包。

    当项目发布到服务器后,src就相当于classes。

    代码如下:
        1、先得到Class,再得到ClassLoader
            ClassLoader cl = this.getClass().getClassLoader();
        2、调用其getResourceAsStream(),得到一个InputStream
            InputStream input = cl.getResourceAsStream();(ClassLoader获取资源时,不能以斜线开头)


        如果不使用ClassLoader,使用Class来获取路径,代码如下:
        1、相对当前.class文件所在目录(不加" / ")相当于:G:apache-tomcat-7.0.72webappsServletDemoWEB-INFclasseswwwwycServletDemo.class
            InputStream input = c.getResourceAsStream("a.txt");
        2、相对于classes目录(加" / ")相当于:G:apache-tomcat-7.0.72webappsServletDemoWEB-INFclasses
            InputStream input = c.getResourceAsStream("/a.txt");
        此时的路径是相对于class所在文件夹。

    打印输出:
        byte[] b = new byte[1024];
        int length = -1;
        while((length = is.read(b)) != -1)
        {
            s(new String(b,0,length,"utf8"));
        }

        不能使用绝对路径,也就是不能带盘符。
        ServletContext的getContextPath()可以获取项目根目录。
        ServletContext的getRealPath()方法可以获取项目的真实目录。 

——request(封装了客户端所有的请求数据)

服务器处理请求的流程:
    服务器每次收到请求时,都会为这个请求创建一个新的线程。
    服务器会把客户端的请求数据封装到request对象中,request就是请求数据的载体。
    服务器还会创建response对象,这个对象与客户端连接在一起,它负责给客户端发送响应。

请求响应流程图
    图片

请求行
请求头
空行
请求体(GET请求无请求体,参数在URL中)

协议中的数据都可以通过request对象来获取。
1、获取常用信息
    1)获取客户端IP地址
        案例:封IP地址。
        String getRemoteAddr()

            String addr = request.getRemoteAddr();
            if(addr.equals("127.0.0.1"))
            {
                System.out.println("本地主机访问AServlet");
            }
            else
            {
                System.out.println("访问错误");
            }

    2)获取HTTP请求方式
        request.getMethod()
            可能是POST也可能是GET

2、获取HTTP请求头
    1)String getHeader(String name):适用于单值头
    2)int getIntHeader(String name):适用于单值int类型的请求头
    3)long getDateHeader(String name):适用于单值毫秒类型的请求头
    4)Enumeration<String> getHeaders(String name):适用于多值请求头

    需要使用链接才会出现防盗链。

    案例:
        1)通过User-Agent识别用户浏览器类型
            String address = request.getRemoteAddr();//获取客户端的IP地址
            System.out.println("IP:" + address);
            System.out.println("请求方式:" + request.getMethod());//获取请求方式
            String userAgent = request.getHeader("User-Agent");//获取名为User-Agent的请求头
            System.out.println(userAgent);
        2)防盗链:如果请求不是通过本站的链接发出的,发送错误状态码404
            String referer = request.getHeader("Referer");
            System.out.println(referer);
            if(referer == null || !referer.contains("localhost"))
                response.sendRedirect("http://www.baidu.com");
            else
                System.out.println("hello world");
3、获取请求URL
    1)String getScheme()
        获取协议:http

    2)String getServerName()
        获取服务器名(主机名称):localhost

    3)String getServerPort()
        获取服务器端口:8080

    4)String getContextPath()
        获取项目名:/HelloWeb(带斜线)
        使用它获取项目名,例如:<a href="项目名+servlet路径"></a>

    5)String getServletPath()
        获取Servlet路径:/AServlet(带斜线)

    6)String getQueryString()
        获取参数部分,即问号后面的部分:username=xxx&password=yyy

    7)String getRequestURI()
        获取请求URI(项目名+Servlet路径就是URI):/HelloWeb/AServlet

    8)String getRequestURL()
        获取请求URL(不包含参数的整个请求路径):http://localhost:8080/HelloWeb/AServlet

4、获取请求参数
    请求参数是由客户端发送给服务器的,有可能是在请求体中(POST),也有可能是在URL之后(GET),获取方式完全相同。

    请求参数:有一个参数一个值的,也有一个参数多个值的。

    1)String getParameter(String name):获取指定名称的请求参数,适用于单值请求参数。

    2)String[] getParameterValues(String name):获取指定名称的请求参数值,适用于多值请求参数。

    3)Enumeration<String> getParameterNames():获取所有请求参数名称。

    4)Map<String,String[]> getParameterMap():获取所有请求参数。
    以上两种方法获取参数方式相同,但是处理编码问题的方式不同。 


    案例:
        1)超链接参数
        2)表单数据

5、请求转发和请求包含

    重定向:多个请求,请求多个Servlet
    请求转发:一次请求,包含多个Servlet

    RequestDispatcher rd = request.getRequestDispatcher("/MyServlet");
        MyServlet:被包含、被转发的Servlet路径。

        使用request获取RequestDispatcher对象,方法的参数是被转发或包含的Servlet的路径。

        请求转发:rd.forward(request, response);
        请求包含:rd.include(request, response);

    请求转发:URL路径不变。
    重定向:URL发生变化。

    有时一个请求需要多个Servlet协作才能完成,所以需要从一个Servlet跳到另一个Servlet来执行。
        *  一个请求跨多个Servlet,需要使用转发和包含。
        *  请求转发:由下一个Servlet完成响应体,当前Servlet可以设置响应头(保留请求头,不保留请求体,因为请求需要被转发)
        *  请求包含:由两个Servlet共同完成响应体(保留请求头并且保留请求体)
        *  无论是请求转发还是请求包含,都在一个请求范围内,使用同一个request和response。

    forward请求转发:
        System.out.println("OneServlet...");
        response.setHeader("aaa", "AAA");
        response.getWriter().print("hello OneServlet");
        //转发
        request.getRequestDispatcher("/TwoServlet").forward(request, response);
        ------->>>>>转发
        System.out.println("TwoSerlet...");
        response.getWriter().print("TwoServlet");

    请求转发调用的是doGet方法还是doPost方法呢?
        调用的是doGet方法
 
    图片

6、request域

    Servlet中四大域对象:pageContext、request、session、application

    只要是域对象,都有以下三个方法:
    void setAttribute(String name, Object value)
    Object getAttribute(String name)
    void removeAttribute(String name)

    同一请求范围内使用request、setAttribute()、request.getAttribute()来传值。
    前一个Servlet调用setAttribute()来保存值,后一个Servlet调用getAttribute()来获取值。

    请求参数不是域属性。

7、请求转发和重定向的区别
    *  请求转发时一个请求一次响应,而重定向是两次请求两次响应。
    *  请求转发地址栏不变化,而重定向会显示后一个请求的地址。(因为重定向是客户端发起的请求)
    *  请求转发只能转发到本项目的Servlet,而重定向不止能重定向到本地项目的其他Servlet,还能定向到其他项目的Servlet,可以重定向到网络中任意URL。(重定向是浏览器执行)
        请求可以只转发给同一应用程序上下文根中的组件,而不在应用程序之间转发。
        转发是Servlet具体实现的,是服务器内部的转发。
        重定向是浏览器发出的请求,包含requestURI,即包含项目名,可以访问其他服务器。
    *  请求转发是服务器端的行为,只需给出转发的Servlet路径即可,而重定向需要给出requestURI,即包含项目名。
    *  请求转发和重定向的效率谁高
        请求转发效率高,因为只有一个请求。
        如果需要地址栏发生变化,则必须使用重定向。
        如果需要在下一个Servlet中获取request域的属性,必须使用请求转发。

——response(其类型是HttpServletResponse)

ServletResponse:与HTTP协议无关的类型。
HttpServletResponse:与HTTP协议相关的类型。

1、状态码
    200表示成功
    302表示重定向
    404表示访问资源不存在(客户端错误)
    500表示服务器错误(代码异常)

    1)sendError(int sc):发送错误状态码,例如404,500
    2)sendError(int sc,String msg):发送错误状态码,可以发送一个错误信息
    3)setStatus(int sc) :发送正确的状态码,可以用来发送302

2、响应头
    Content-Type:响应类型
    Refresh:刷新
    Location:URL

    头就是一个键值对,可能会存在一个名字一个值的情况,也可能会存在一个名字多个值的情况(例如编码格式)。

    1)setHeader(String name, String value):适用于单值的响应头
        response.setHeader("aaa","AAA");
        如果name相同,则设置第二个值时,会覆盖第一个值,此特性与Map集合相同,如果想要设置第二个值,需要使用addHeader()方法。

    2)addHeader(String name,String value):适用于多值的响应头(例如设置多个字符编码)
        response.add("aaa","A");
        response.add("aaa","AA");
        执行此方法之前不需要执行setHeader方法。

    3)setIntHeader(String name,int value):适用于单值的int类型的响应头
        response.setIntHeader("Content-Length",100);

    4)addIntHeader(String name,int value):适用于多值的int类型的响应头

    5)setDateHeader(String name,long value):适用于单值的毫秒类型的响应头
        response.setDateHeader("expires",1000*60);
        long:表示时间的毫秒数
        expires:过期时间,-1表示不缓存。

    6)addDateHeader(String name,long value):适用于多值的毫秒类型的响应头

    案例:
        1)发送302,设置Location头信息,完成重定向。
            即使重定向代码被执行,后面的代码依然会被执行。 
            图片
            /**
             * 演示重定向
             * 用户请求BServlet,然后BServlet响应302,返回Location头
             * @author WYC
             *
             */
            public class BServlet extends HttpServlet {
                public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
                    System.out.println("BServlet");
                    /**
                     * 重定向
                     * 1、设置Location
                     * 2、发送状态码
                     */
                    //因为是浏览器发出的请求,所以必须是Servlet路径,与web.xml中的Servlet路径相对应
                    //    项目名/Servlet路径    统称请求URI
                    //    URL带IP(主机名),URI不带IP(主机名)
                    response.setHeader("Location", "/day10_1/CServlet");
                    response.setStatus(302);
                }
            }

        2)定时刷新,设置Refresh头信息(可以理解为定时刷新)
            /**
             * 演示定时刷新
             * 设置一个Refresh,它表示定时刷新
             */
            public class DServlet extends HttpServlet {
 
            public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                request.setCharacterEncoding("utf-8");
                response.setCharacterEncoding("utf-8");
                //下面代码用来发送响应体
                PrintWriter writer = response.getWriter();
                writer.print("5秒钟后自动跳转到主页");
                //设置名为Refresh的响应头
                response.setHeader("Refresh", "5;url=/day10_1/CServlet");    //设置URI
            }
        }

        3)禁用浏览器缓存:Cache-Control、pragma、expires(这三个响应头适用于所有的HTTP协议版本)
            //设置禁用缓存
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("pragma", "no-cache");
            response.setDateHeader("expires", -1);

            <meta>标签可以代替响应头:
                <meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8”>    //只要涉及到文本,就需要带上charset

3、响应体
    通常是HTML,也可以是图片。
    在ServletResponse中有两个方法
        PrintWriter getWriter() throws IOException 
            获取字符流:PrintWriter writer = responsr.getWriter();
            用来发送字符相关的数据。
            异常:
                IllegalStateException
                    非法状态异常,在非法或不适当的时间调用该方法时抛出的异常,换句话说,即Java环境或Java应用程序没有处于请求操作所要求的适当状态下而调用该方法,会抛出该异常。
                    异常条件:
                        当已经使用字节流时,再使用字符流,会抛出该异常。也就是说,字节流和字符流只能使用其一。
                        getOutputStream条件相同。
                    其父类是RuntimeException。

        ServletOutputStream getOutputStream() throw IOException
            获取Servlet输出字节流:ServletOutputStream out = response.getOutputStream();
            使用这个字节流可以发送字节相关的数据。

4、重定向
    方法一:设置302,设置Location,其中变化的只有Location,所以Java将该方法封装,完成重定向。
    方法二:sendRedirect(String location);
        response.sendRedirect("/day10_1/CServlet");


——编码

Tomcat8.0默认编码改为:UTF-8

常见字符编码:ISO-8859-1(不支持中文)、GB2312、GBK(系统默认编码,中国国标码)、utf-8(万国码,支持全世界的编码)

一、响应编码
    1、在使用response.getWriter()来向客户端发送字符数据时,如果在之前没有设置编码,那么默认使用ISO-8859-1,因为ISO-8859-1不支持中文,所以一定会乱码。

    2、在使用response.getWriter()之前可以使用response.setCharacter()来设置相应编码,当设置响应编码后,发送的全部字符都是通过指定编码进行编码的字符。

    3、在使用response.getWriter()之前可以使用response.setHeader("Content-Type","text/html;charset=utf-8")来设置响应头,来告诉浏览器使用什么编码进行解码。相当于同时设置了:setCharacterEncoding("utf-8");

    4、setHeader("Content-Type","text/html;charset=utf-8")的快捷方法是:setContentType("text/html;charset=utf-8");

        图片

二、请求编码
    1、客户端发送给服务器的请求参数是什么编码
        客户端首先要打开一个页面,然后在页面中提交表单或点击超链接,在请求这个页面时,服务器响应的编码是什么,那么客户端发送时请求的编码就是什么。

        也就是说,当前页面的编码,是由上一个页面响应的编码来指定的。 
        所以,响应编码必须一致,才能保证不出现乱码的情况。

    2、服务器端默认使用什么编码来解码参数
        服务器端默认使用iso-8859-1来解码,所以一定会出现乱码,因为iso-8859-1不支持中文。

        解码:字节 → 字符串
        编码:字符串 → 字节

    3、请求编码处理分为两种:GET和POST
        GET请求参数不在请求体中,而POST请求参数在请求体中,所以它们的处理方式是不同的。

    4、GET请求编码处理:
        1)String username = new String(Request.getParameter("username").getBytes("iso-8859-1"),"utf-8");
        因为服务器默认使用ISO-8859-1编码,所以使用ISO-8859-1编码进行解码。 
        2)在server.xml文件中配置UTIEncoding=utf-8
            图片

            当设置编码之后,GET请求参数的编码就不需要手动设置了,默认为UTF-8。
            但是不推荐使用,因为不灵活。

            推荐使用如下代码:
            String name = request.getParameter("name");
            byte[] bytes = name.getBytes("ISO-8859-1");    //先使用iso-8859-1解码
            name = new String(bytes,"utf-8");    //再使用utf-8编码

    5、POST请求编码处理:
        1)String username = new String(request.getParameter("iso-8859-1"),"utf-8");    //先得到参数,然后回退为字节数组,然后重新编码。
        2)在获取参数之前调用request.setCharacterEncoding("utf-8");
        代码如下:
        //1、在获取参数之前,需要先调用request.setCharacterEncoding("utf-8");

        //2、使用getParameter()来获取参数
        request.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        System.out.println(username);


        图片

三、URL编码

    表单的编码类型:Content-Type:application/x-www-form-urlencoded,就是把中文转换成%+两位的16进制数。

    为什么要用它呢?
        在客户端和服务器之间传递中文时,需要把它转换成适合网络传输的方式。
        如果直接传输中文,可能会出现丢失数据的情况,为了避免这种情况,所以先将中文转换成16进制数,再发送给服务器。
        服务器可以自动识别URL编码。

    1、它不是字符编码
    2、它是用来在客户端与服务器之间传递参数用的一种方式。
    3、URI编码需要先指定一种字符编码,把字符串解码后,得到byte[]数组,然后把小于0的字节+256,然后转换成16进制,再在前面加上一个%。
    4、POST请求默认使用URL编码,Tomcat会自动使用URL解码
    5、URL编码:String username = URIEncoder.encode(username,"utf-8");
        字节 → URL编码(%A5....) 
        代码如下:
            String name = "啊啊";
            byte[ ] bytes = name.getBytes("utf-8");
            System.out.println(Array.toString(bytes)):    //该语句会将已经转换为字节数组的"啊啊"打印到控制台
            String s = URLEncoder.encode(name,"utf-8");    //将字节数组按照utf-8的编码转换为URL编码

        页面超链接中的中文必须URL编码,服务器不会自动编码,但是服务器会自动解码。

    6、URL解码:String username = URLDecoder.decode(username,"utf-8");
        URL编码(A5....)→ 字符串
        代码如下:
            s = URLDecoder.decode(s,"utf-8");
            //该语句将URL编码转换为utf-8编码(也就是从%E2%C3%A0转到中文汉字)

    如果使用的是GET请求,并且链接中有中文参数,需要使用URL进行编码。


——路径

建议使用绝对路径,绝对路径不受界面影响。

一、web.xml中<url-pattern>路径(Servlet路径)
    要么以" * "开头,要么以" / "开头。
    一般Servlet路径中不适用通配符,在使用过滤器的时候才会使用通配符。

二、转发和包含路径
    以" / "开头:相对当前项目路径,例如:
        http://localhost:8080/项目名/AServlet
        request.getRequestDispatcher("/AServlet").forward(request,response);
    如果不以" / "开头:相对当前Servlet路径名,例如:
        访问同目录下的Servlet:
            request.getRequestDispatcher("BServlet").forward(request,response);

三、重定向路径(客户端路径)
    以" / "开头:相对当前主机,例如http://localhost:8080/,所以需要自己手动添加项目名。
    例如:
        response.sendRedirect("/HelloWeb/AServlet");

四、页面中超链接和表单路径
    与重定向相同,都是客户端路径,需要添加项目名。
    <form action="/HelloWeb/AServlet" ></form>
    <a href="/HelloWeb/AServlet">
    <a href="AServlet">:如果不以" / "开头,那么就是相对路径。

五、ServletContext获取资源路径
    相对于当前项目目录,即WebRoot目录下。
    getContextPath(),获取项目名。 

六、ClassLoader获取资源路径 
    相对classes目录。(类路径)
    当项目发布到服务器,src就相当于classes。
    负责加载类,同时可以获取类路径下其他资源。
        //得到类加载器,让类加载器去类路径下获取资源(ClassLoader获取资源时,不能以斜线开头)

        InputStream i = this.getClass().getClassLoader().getResourceAsStream("a.txt");
        byte[] b = new byte[1024];
        int length = i.read(b);

        s(new String(b, 0, length, "utf8")); 

七、Class获取资源路径
    以" / "开头:相对classes目录。
    不以" / "开头相对当前.class文件所在目录。 

        Class c = this.getClass();
        InputStream i = c.getResourceAsStream("/a.txt");    //使用类获取当前classes目录下的文件时,必须以/开头
                                                                                          //如果不写/,则会到当前Class文件所在的目录去加载 
        byte[] b = new byte[1024];
        int length = i.read(b);
        s(new String(b, 0, length, "utf8"));


——BaseServlet

1、我们希望在一个Setvlet中可以有多个请求处理方法,而不是一个请求对应一个Setvlet。
2、客户端发送请求时,必须多给出一个参数,用来说明要调用的方法。
    请求处理方法的签名必须与service相同,即返回值、参数以及声明的异常都相同。
3、客户端必须传递名为method()的参数


domain: User
dao: UserDao
setvice: UseService
servlet: UserServlet

生命周期方法:
    void init(ServletConfig config)
    void destory()
    void service(ServletRequest, ServletResponse) throws IOException, ServletException {
        在这里让同一个请求来调用其他方法。
        要求:
            用户发出请求时,必须给出一个参数,用来说明要调用哪一个方法。
        // 获取参数,通过参数来确定要调用的方法
    }

===============================================================================

4、TestServlet

import java.io.IOException;
import java.lang.reflect.Method;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * 在这里给出多个请求处理方法 请求处理方法: 除了名称以外,都与service方法相同。
 */
 
public class AServlet extends HttpServlet {
 
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*
         * 1、获取参数,用来识别用户想请求的方法
         * 2、判断参数指定的是哪一个方法,并调用
         */
        /*
         * String method = request.getParameter("method"); if
         * (method.equals("addUser")) { addUser(request, response); } else if
         * (method.equals("editUser")) { editUser(request, response); } else if
         * (method.equals("deleteUser")) { deleteUser(request, response); }
         */
        /*
         * 但是发现通过if else 来进行判断,无法实现扩展性,并且代码不可重用
         * 所以: 得到方法名称,然后通过反射来调用方法
         * 1、得到方法名,通过方法名得到Method类的对象
         *    * 需要得到Class对象,然后调用该对象的方法得到Method对象
         * 我们要查询的是当前类的方法,所以我们需要得到当前类的Class对象
         */
        String methodName = request.getParameter("method");
 
        if (methodName == null || methodName.trim().isEmpty()) {
            throw new RuntimeException("未传递method参数");
        }
 
        Class c = this.getClass();
        // 通过方法名、参数类型来获得指定方法
        Method method = null;
        try {
            method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
        } catch (Exception e) {
            throw new RuntimeException("方法不存在");
        }
 
        /*
         * 调用method对象指定的方法
         */
        try {
            method.invoke(this, request, response);
        } catch (Exception e) {
            throw new RuntimeException(methodName + "方法内部抛出异常");
        }
    }
 
    public void addUser(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("addUser()...");
    }
 
    public void editUser(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("editUser()...");
    }
 
    public void deleteUser(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("deleteUser()...");
    }
 
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doGet()...");
    }
}

------------------------------------------------------------------------------------------------------------------------------

5、BaseServlet

    如果想让所有的Setvlet都可以使用这种方法,可以创建一个抽象类:BaseSetvlet,使用BaseServlet中的service(HttpRequestServlet, HttpResponseSevlet)进行方法调用,即其他Servlet继承BaseServlet即可。

import java.io.IOException;
import java.lang.reflect.Method;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public abstract class BaseServlet extends HttpServlet {
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*
         * 1、获取参数,用来识别用户想请求的方法
         * 2、判断参数指定的是哪一个方法,并调用
         */
 
        /*
         * 得到方法名称,然后通过反射来调用方法
         * 1、得到方法名,通过方法名得到Method类的对象
         *     * 需要得到Class对象,然后调用该对象的方法得到Method对象
         * 2、我们要查询的是当前类的方法,所以我们需要得到当前类的Class对象
         */
        String methodName = request.getParameter("method");
 
        if (methodName == null || methodName.trim().isEmpty()) {
            throw new RuntimeException("未传递method参数");
        }
 
        Class c = this.getClass();
        // 通过方法名、参数类型来获得指定方法
        Method method = null;
        try {
            method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
        } catch (Exception e) {
            throw new RuntimeException("方法不存在");
        }
 
        /*
         * 调用method对象指定的方法
         */
        try {
            method.invoke(this, request, response);
        } catch (Exception e) {
            throw new RuntimeException(methodName + "方法内部抛出异常");
        }
    }
}

===============================================================================

6、BaseSetvlet进阶

    Servlet常用语转发或重定向,可以使用BaseSetvlet来完成,只需要在指定方法调用结束后返回字符串即可,字符串格式:
        "前缀:后缀";
    前缀:确定转发还是重定向
    后缀:确定路径

import java.io.IOException;
import java.lang.reflect.Method;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public abstract class BaseServlet extends HttpServlet {
 
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*
         * 1、获取参数,用来识别用户想请求的方法
         * 2、判断参数指定的是哪一个方法,并调用
         */
 
        /*
         * 得到方法名称,然后通过反射来调用方法
         * 1、得到方法名,通过方法名得到Method类的对象
         *     *   需要得到Class对象,然后调用该对象的方法得到Method对象
         * 2、我们要查询的是当前类的方法,所以我们需要得到当前类的Class对象
         */
        String methodName = request.getParameter("method");
 
        if (methodName == null || methodName.trim().isEmpty()) {
            throw new RuntimeException("未传递method参数");
        }
 
        Class c = this.getClass();
        // 通过方法名、参数类型来获得指定方法
        Method method = null;
        try {
            method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
        } catch (Exception e) {
            throw new RuntimeException("方法不存在");
        }
 
        /*
         * 调用method对象指定的方法
         */
        try {
            /*
             * 获取请求处理方法执行后返回的字符串 它表示转发或重定向的路径
             */
            String result = (String) method.invoke(this, request, response);
            /*
             * 帮Servlet完成转发或重定向 1、如果返回的字符串为null或"",则不进行处理。
             * 1、查看返回的字符串是否包含冒号,如果没有,表示转发(默认)
             * 2、如果有冒号,使用冒号分隔字符串,得到前缀和转发(重定向)路径
             * 3、如果前缀是f,表示转发,如果前缀是r,表示重定向。
             */
            if (result == null || result.trim().isEmpty()) {
                return;
            }
            if (result.contains(":")) {
                // 使用冒号分割字符串,得到前缀和路径
                String[] ss = result.split(":");
                if (ss[0].equals("f")) {
                    // 请求转发
                    request.getRequestDispatcher(ss[1]).forward(request, response);
                } else if (ss[0].equals("r")) {
                    // 请求重定向
                    response.sendRedirect(request.getContextPath() + ss[1]);
                } else {
                    throw new RuntimeException("您指定的操作:" + ss[0] + ",当前版本还不支持!");
                }
 
            } else { // 没有冒号,默认为转发
                request.getRequestDispatcher(result).forward(request, response);
            }
 
        } catch (Exception e) {
            System.out.println(methodName + "方法内部抛出异常");
            throw new RuntimeException(e);
        }
    }
}

------------------------------------------------------------------------------------------------------------------------------

import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class BServlet extends BaseServlet {
 
    public String fun1(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("fun1()...");
        return "f:/index.jsp";
    }
 
    public String fun2(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("fun2()...");
        return "r:/index.jsp";
    }
 
    public String fun3(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("fun3()...");
        return "";
    }
 
    public String fun4(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("fun4()...");
        return "d:/WEB-INF/img.jpg";
    }
}
原文地址:https://www.cnblogs.com/wwwwyc/p/6375263.html