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学习笔记_第1张图片 
 
——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与一个路径绑定在一起)
           
        2)浏览器访问Servlet路径。

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


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

       
             
            information
             
            com.wyc.servlet.information
        
        
        
             
            information
             
            /information
       

        分析可能出现的错误
            ①hahahaha,名字不一样,会在启动Tomcat的时候报错。
            ②com.wyc.hahaha,如果加上.java扩展名,会出现500错误。
            ③写错资源名: http://localhost:8080/wycweb/资源名  也就是,会出现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()
            返回的就是XXX的值。
            并不提供获取Servlet类的方法,因为JDK认为这样是不安全的。

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

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

           
                p1
                v1
           

           
                p2
                v2
           

        

        如果想要获取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 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()方法需要我们自己重写。

Servlet学习笔记_第2张图片

    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会自动创建下图所示目录结构:
                 Servlet学习笔记_第3张图片
        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:\Windows\Fonts,找到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的初始化。

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

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

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

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

    代码如下:
       
            XXServlet01
            com.wyc.servlet.XXServlet01
            0
        

       
            XXServlet01
            /XXServlet01
       


       
            XXServlet02
            com.wyc.servlet.XXServlet02
            1
        

        
            XXServlet02
            /XXServlet02
        


        
            XXServlet03
            com.wyc.servlet.XXServlet03
            2
        

        
            XXServlet03
            /XXServlet03
        
 

三、
    的子元素,用来指定Servlet的访问路径,即URL,它必须是以"/"开头。

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

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

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

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

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

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

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

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

    5、在Servlet映射到URL中的时候也可以使用通配符(* ),但是只能有两种固定的格式,一种格式是“ * . 扩展名 ”:
            /*  或  *.html
        另一种格式是以斜线(/)开头,以(/*)结尾。代码如下
            /*        /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_HOME\conf\web.xml文件中的内容,相当于写到了每个项目的web.xml文件中,它是所有web.xml文件的父文件。

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

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

——Servlet与反射

    当服务器获取到URL时,会截取Servlet路径与进行匹配,如果匹配成功,找到对应的,可以通过
        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才能获取。

   
       
            paramName01
            paramValue01
       

       
            paramName02
            paramValue02
       

   


    获取参数方法:
        当前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:\tomcat7\webapps\HelloWeb\a.txt
            2)获取b.txt的真实路径:String realPath = ServletContext.getRealPath("/WEB-INF/b.txt");
                realPath的值为:F:\tomcat7\webapps\HelloWeb\WEB-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.72\webapps\ServletDemo\WEB-INF\classes\www\wyc\ServletDemo.class
            InputStream input = c.getResourceAsStream("a.txt");
        2、相对于classes目录(加" / ")相当于:G:\apache-tomcat-7.0.72\webapps\ServletDemo\WEB-INF\classes
            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对象,这个对象与客户端连接在一起,它负责给客户端发送响应。

请求响应流程图
     Servlet学习笔记_第4张图片

请求行
请求头
空行
请求体(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 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(带斜线)
        使用它获取项目名,例如:

    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 getParameterNames():获取所有请求参数名称。

    4)Map 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方法
 
     Servlet学习笔记_第5张图片

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头信息,完成重定向。
            即使重定向代码被执行,后面的代码依然会被执行。 
             Servlet学习笔记_第6张图片
            /**
             * 演示重定向
             * 用户请求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);

            标签可以代替响应头:
                    //只要涉及到文本,就需要带上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");

         Servlet学习笔记_第7张图片

二、请求编码
    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);


        Servlet学习笔记_第8张图片

三、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中路径(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");

四、页面中超链接和表单路径
    与重定向相同,都是客户端路径,需要添加项目名。
   


   
   
:如果不以" / "开头,那么就是相对路径。

五、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

你可能感兴趣的:(Servlet学习笔记)