Head First Servlet && JSP读书笔记

Head First Servlet && JSP

image-20210817173807902

引子

  • Sun 考试-SCJP->SWCCD,$200
  • 学习原则
    • 图胜于文
    • 采用交谈式学习风格
    • Keep Attention,Think Deeply and Keep On
    • 影响读者情绪
  • 学习规范
    • Slow。理解的越多,记忆的越少
    • 记笔记或将给其他人听
    • 多喝水

1、前言与概述

GET和POST区别

  • 请求方式:GET请求会把表单数据加到URL最后,POST请求会放在请求体中
  • 数据量:GET有长度限制
  • 安全性:GET发送数据会追加在URL中,不适合发送敏感数据;POST请求安全
  • 幂等性:一般来说GET是幂等的,POST不是幂等的,即你可以一遍一遍反复做同一件事,而不会产生意想不到的结果,例子:网络卡的时候多次付款操作应视为只有一次。这是一般的规范,由程序员自己注意并实现
  • 书签:get请求可以建立书签,post请求不可以建立书签

Servlet和JSP的关系

  • Servlet接受来自用户浏览器发送的请求,并最终返回响应;
  • JSP根据Servlet的请求(request)内容创造动态网页作为相应。
  • JSP优化了Servlet中out.println输出html的繁琐,利用请求转发将请求交给JSP,作出响应。

2、Web应用体系结构

  • Web体系结构解决如下问题:

    • HTTP方法对应的Servlet处理方式(方法名、返回值)
    • Servlet的生命周期
    • 构建Web应用需要部署的目录
  • 部署文件的语义(Servlet实例、名、类、初始化参数、URL映射)

容器

Servlet没有main()方法,受控于另一个Java应用。如Tomcat

  • Web容器作用

    • 实现Servlet与web服务器的对话。

    例如监听端口

    • 负责Servlet的整个生命周期。

      • 封装请求(HttpServletRequest、HttpServletResponse)
      • 分配线程(查找配置文件,处理)
      • 使用特定的方法处理(Post、Get等)
    • 多线程支持。

    请求到来时,至少要创建一个线程来处理这个请求。

    • 声明式使用安全。

      • web容器采用xml配置来保障安全。
      • 更安全
      • 避免硬编码(无须频繁变动代码)
    • jsp支持。

  • 容器如何处理请求

    • 用户点击一个链接,其url指向一个servlet而不是静态页面;
    • 容器判断出来这个请求需要servlet,创建两个对象HttpServletResponse和HttpServletRequest;
    • 容器一句请求的url找到正确的url,创建和分配一个线程,并把请求和响应对象传递给这个servlet;
    • 容器调用service()方法,依据种类不同调用doGet和doPost()方法;
    • doGet方法生成动态页面,并把这个页面填入响应对象;
    • 线程结束,容器将响应对象转换为HTTP响应,返还给客户,然后删除请求和响应对象。

Servlet

  • CGI(Common Gateway Interface)通用网关接口,描述了服务器和请求处理程序之间传输数据的一种标准,在之前,CGI是作为动态Web服务器的主要标准,通常使用Perl编写ServletCGI都是在Web服务器中扮演辅助应用的角色,通过ServletCGI`,能够使Web服务器动态返回结果。

  • 使用Servlet的时候,我们需要引入javax.servlet.*这个包,在这个包中我们可以找到一个名为Servlet的接口,这便是Servlet

package javax.servlet;
import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

可以看到Servlet就是一个简单的接口而已,这个接口用来支持使用Java语言编写能够交互式的浏览和修改数据的服务端程序

  • 为什么要使用Servlet

    如果要编写一个Web服务器,我们需要从Socket开始,监听Socket,开启多线程,处理Socket请求等,如果你有实力,并且能够处理好一系列的问题,比如高并发,IO阻塞等,是完全不用Servlet的,也不需要什么Tomcat容器,但是这些工作门槛高,并且都是通用的,因此就有大牛做出一个通用的,高性能的容器,这个容器能够帮你监听Socket,开启并管理多线程,已经处理请求,而不同的业务逻辑则可以使用Servlet编写,容器不用关心Servlet的实现,它只需要在需要的时候调用所配置的Servlet对象即可。而Servket也不用关心监听Socket等,只用更好的关心业务逻辑即可。

    总结便是:Servlet是作为Web容器和业务逻辑的桥梁。

  • Servlet配置

    • 配置Servlet有3大属性,Servlet的名字,Servlet所对应的类,Servlet所对应的Url,UrlServlet所对应的类是分开配置的,通过Servlet名字建立联系,为什么分开配置,是这样因为前台人员只用关心Servlet对应的Url,而后台开发人员就只用关心Servlet所对应的类即可
 <servlet>
    <servlet-name>Chapter1 Servlet</servlet-name>
    <servlet-class>cn.servlet.Ch1Servlet</servlet-class>
  </servlet>
   <!------------------分隔符-------------------->       
  <servlet-mapping>
    <servlet-name>Chapter1 Servlet</servlet-name>
    <url-pattern>/Servlet</url-pattern>
  </servlet-mapping>

3、MVC迷你教程

MVC思想

  • M(model):可复用的处理业务的类文件;V(version):JSP;C(controller):servlet。
  • 视图 – 控制器(MVC)就是把业务逻辑从servlet中抽出来,把它放在一个“模型”中。

Servlet项目部署规范

image-20210817174554816

约定大于配置

简单总结如下:

  • 在Tomcat容器中,所需要部署的服务,应该放在Tomcat -> webapps目录下

  • 用户无法访问WEB-INF文件夹以及文件下的任何东西,换句话说,用户可以直接访问项目中除WEB-INF目录下的任何文件

  • WEB-INF

    目录下一般分3个文件

    • classes文件夹:Servlet逻辑处理class文件夹
    • lib文件夹:classes文件夹中所依赖的其他文件
    • web.xml:DD,(Deployment Descriptor)部署描述文件

Tomcat目录结构

  • image-20210817164613316
  • bin用来存放Tomcat的命令,包括.sh和.bat结尾的命令。
  • conf是指Tomcat的配置文件
  • lib用来存放jar包,比如说与数据库的连接jar包就可以放到这里
  • logs用来存放日志
  • temp用来存放临时文件
  • webapps和wtpwebapps用来存放编译后的项目。
  • work是Tomcat的缓存
  • (backup 用于Tomcat的备份)

Web.xml配置详解

context-param

作用:该元素用来声明应用范围(整个WEB项目)内的上下文初始化参数。

param-name 设定上下文的参数名称。必须是唯一名称

param-value 设定的参数名称的值

初始化过程:

    1. 在启动Web项目时,容器(比如Tomcat)会读web.xml配置文件中的两个节点和。
    2. 接着容器会创建一个ServletContext(上下文),应用范围内即整个WEB项目都能使用这个上下文。
    3. 接着容器会将读取到转化为键值对,并交给ServletContext。
    4. 容器创建中的类实例,即创建监听(备注:listener定义的类可以是自定义的类但必须需要继承ServletContextListener)。
    5. 在监听的类中会有一个contextInitialized(ServletContextEvent event)初始化方法,在这个方法中可以通过event.getServletContext().getInitParameter(“contextConfigLocation”) 来得到context-param 设定的值。在这个类中还必须有一个contextDestroyed(ServletContextEvent event) 销毁方法.用于关闭应用前释放资源,比如说数据库连接的关闭。
    6. 得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早。

由上面的初始化过程可知容器对于web.xml的加载过程是context-param >> listener >> fileter >> servlet

<context-param>
    <param-name>context/param</param-name>
    <param-value>avalible during application</param-value>
</context-param>

mime-mapping

作用:TOMCAT在默认情况下下载.rar的文件是把文件当作text打开,以至于IE打开RAR文件为乱码,所以配置mime-mapping,它的作用是用于规定下载格式

<mime-mapping>
    <extension>rar</extension>
    <mime-type>application/rar</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>txt</extension>
    <mime-type>applcation/txt</mime-type>
</mime-mapping>

error-page

作用:当程序出现404或者nullpointException时需要跳转的页面

<error-page> 
    <error-code>404</error-code> 
     <location>/error.jsp</location> 
</error-page>

init-param

作用:配置初始化参数

<servlet>
    <servlet-name>chapter2</servlet-name>
    <servlet-class>com.example.web.BeerSelect</servlet-class>
  <init-param>
     <param-name>param1</param-name>
     <param-value>avalible in servlet init()</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>chapter2</servlet-name>
    <url-pattern>/SelectBeer.do</url-pattern>
</servlet-mapping>

welcome-file

作用:项目启动时跳转的页面;

注意:welcome-file-list可以有多个welcome-file,先找第一个,如果存在就显示,不再继续往下找,反之不存在就顺序往下找,直到找到为止 ;

<welcome-file-list>
    <welcome-file>aa.html</welcome-file>
    <welcome-file>form.html</welcome-file>
</welcome-file-list>

servlet,servlet-mapping,servlet-class,servlet-name

作用:
   这个是我们要注册servlet的名字,一般跟Servlet类名有关
   这个就是指向我们要注册的servlet 的类地址, 要带包路径
   是用来配置我们注册的组件的访问路径,里面包括两个节点
   这个要与 前面写的servlet那么一直
   配置这个组件的访问路径

<servlet>
    <servlet-name>chapter2</servlet-name>
    <servlet-class>com.example.web.BeerSelect</servlet-class>
  <init-param>
     <param-name>param1</param-name>
     <param-value>avalible in servlet init()</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>chapter2</servlet-name>
    <url-pattern>/SelectBeer.do</url-pattern>
</servlet-mapping>

4、请求和响应

Servlet

  • Servlet是一种独立于平台和协议的服务器端的技术,可以生成动态的web页面

  • 任务是得到一个用户的请求,再发回一个响应。

  • Servlet生命周期

    • 用户点击一个链接,链接的URL指向一个Servlet
    • 容器收到请求,根据配置文件找到对应的Servlet类
    • 容器创建两个对象HttpServletResponseHttpServletRequest
    • 容器找到对应的Servlet类,若该类未被实例化,则实例化之
    • 调用对应Servlet类的service()方法,并将HttpServletResponseHttpServletRequest传递给service()方法
    • service()方法完成逻辑调用,并将相应消息通过响应对象返回客户
    • service()方法结束,HttpServletResponseHttpServletRequest销毁,Servlet归还线程

    详细

    1. Servelet接口中,包含3个生命周期的方法

      public interface Servlet {
          
          //初始化
          void init(ServletConfig var1) throws ServletException;
          
          //处理请求
          void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
          
          //销毁   
          void destroy();
      }
      

      其中init()destory()方法在Servlet一生(启动容器至关闭容器)中只会被调用一次。

      service()方法会在用户请求过程中被调用,且会被多线程调用(注意线程安全问题)

    2. 观察Servlet包我们可以发现,我们平时并不是直接使用Servlet,而是使用它的子类HttpServlet

      HttpServlet的直接父类是GenericServlet,为什么会存在3个类呢?

      第一个Servlet是接口,这个是毋庸置疑的,但是他们发现Servlet中会有许多通用方法,于是写了一个抽象的通用类GenericServlet,里面实现了一些通用的方法,由于Servlet一开始设计不只是支持HTTP协议的,因此当需要其他协议也支持Servlet的时候,可以直接继承GenericServlet即可,比如FTPServlet,但是目前只有HTTPServlet,所以我们会看到HttpServlet

    3. 一般来说,一个容器中,只会存在一个对应的Servlet实例,多线程并不是在新线程中新建一个Servlet对象,多线程调用的只是service()方法

    4. init(ServletConfig var1)方法充当的是Servlet的构造方法,那为什么要叫init(ServletConfig var1)而不直接使用构造方法呢?

      因为Servlet是在JDK1.0中发布的,而JDK1.0规定动态加载的Java类的构造方法是不能带有参数的,因此这里只能使用init(ServletConfig var1)代替构造方法

    5. 由于init方法充当了Servlet的构造方法,因此不要在Servlet中添加任何代码,因为容器会先构造Servlet对象,然后才调用init()方法初始化ServletConfig对象,因此构造方法很可能运行时,不小心使用了ServletConfig对象,就得到一个空指针错误

HTTP方法

  • GET:要求得到请求URL的资源或文件

  • POST:要求服务器接受附加在请求体中的信息,并提供请求URL的资源或文件;非幂等

  • HEAD:只要求得到GET返回结果的首部

  • TRACE:要求请求消息回送,这样客户能看到另一端接收了什么,以便测试或排错;

  • PUT:指出要把所包含的信息放在请求的URL上

  • OPTIONS:要求得到一个HTTP方法列表,所请求URL可以作出响应

  • CONNECT:要求连接以便建立隧道

  • 备注:servlet API中没有处理doConnect()的机制,所以HttpServlet里没有doConnect()

  • GET和POST

    • get请求:

      • 简单的超链接往往意味着get请求
      <a href="www.baidu.com">百度</a> 

form表单的method方法指定get请求或者默认为get请求

      <form method="POST" action="selectBee.do">
          <input type="submit"/>
      </form>
  • post请求:

    • form表单的method方法指定post请求
      <form method="POST" action="selectBee.do">
          <input type="submit"/>
      </form>

如果想让servlet同时支持GET和POST请求,怎么做?

public void doPost() throws Exception{
     doGet(req,resp);  
}

HttpServletRequest接口

  • HttpServletRequest继承至ServletRequest,里面包含很多方法,其中主要有歧义的有:

  • getServerPort():获取Server的端口,这里的Server表示相对于客户端来说,所看到的服务器的端口

  • getLocalPort():获取当前的端口,和getServerPort()的区别主要在于当使用负载均衡的时候,getLocalPort()获取的便是服务器实际的端口,而getServerPort()获取的是用户所知道的端口

  • getRemotePort():获取远程端口,Remote指的就是远程,远程相对于服务器来说,就是客户端,也就是说getRemotePort()获取的是用户的端口

注意:在测试的时候,如果使用Nginx转发,则需要在配置文件中加上proxy_set_header Host $host:xxxx,其中xxxx便是真正服务器的端口,否则getLocalPort()getRemotePort()永远都是返回相同的值:用户所知道的端口

  1. getParameter返回值是一个String类型,因此当一个参数被多次赋值的时候,需要使用getParameterValues,得到String[]类型。

    比如:localhost:8080?name=xxx&&name=xxx

    此时可以使用getParameterValues("name")获取所有的值,也可以使用getParamter("name")获取第一个name参数

    getParamter()内部是通过getParameterValues("name")[0]实现,因此可以混用

  2. getIntHeader等价于Integer.parseInt(getHeader())

  3. HttpServletRequest包含getReader()getInputStream()方法,区别在于一个是字符流,一个是字节流

  4. 一般来说,HTTP协议中请求的内容都可以通过HttpServletRequest查询

HttpServletResponse接口

  • HttpServletResponse主要主要是用来写入返回内容的,因此使用的最多的方法便是getOutputStream()getWriter(),同样两者的区别在于一个是字节流,一个是字符流

    getBufferSize()获取目前缓冲区的大小,类似IO中的BufferedOutputStream,Servlet中的流也自带了缓冲,当输入的内容大于这个缓冲的时候,Servlet会先将缓冲的内容写入到Socket中,可以通过isCommite()方法查询是否已经刷新过缓存

    重要:当缓存被刷新了以后,就无法再修改里面的内容,此时某些包含覆盖操作的方法再调用就会报错,比如:setHeader()

    根路径是Servlet规范目录中与WEB-INF目录同级别的地方。Servlet中路径的识别和Linux路径机制一样,以/开始则表示从根目录(项目文件夹:tomcat->webapp->xxx->根目录)开始,不以/开始则表示相对路径。

    setContentType()可以设置MIME类型,MIME(Multipurpose Internet Mail Extensions),多用户互联网邮件扩展类型,常见的有:text/html,application/json等,也可以通过setHeader()方法设置,不过setContentType()在IDE中一般带有拼写检查

    最佳实践:

    首先我们看看有关Buffer的定义:

    isCommitted
    boolean isCommitted()
    

    Returns a boolean indicating if the response has been committed. A committed response has already had its status code and headers written.

    什么意思呢?也就是说当达到flushBuffer的条件的时候,缓冲会首先将statusheaders写入socket中。

    因此对于我们来说,我们最好首先设置statusheader,再设置其他的东西,否则如果在缓冲已经写入后再设置statusheader是无效的。

    setHeader()`和`addHeader()`都可以设置`HTTP`头,不过`setHeader()`会覆盖掉重复的`key`,而`addHeader()`不会覆盖重复的`key
    

    使用了setHeader()后,不论前面有多少个相同key的头,最后都会变成最后一次被set的值

    sendRedirect()发送重定向消息,这种重定向是在客户端中完成的,

    sendRedirect()参数有两种写法,一种是相对路径:不以/开头,一种是绝对路径:以/开头

    如果响应的isCommitted()true,表示这个响应已经提交,此时便不能再调用sendRedirect()

    sendRedirect()方法具有相同的功能的还有forward

    request.getRequestDispatcher(path).forward(request,response)
    

    不过此种方式是通过服务器进行跳转的。

    • redirect 和 include、forward的区别在于forwardinclude会有两次与requestresponse交互,而redirect只有一次
    • includeforward的区别在于include转发后会再次返回到servlet,而forward不会,include相当于包含了另外一个servlet的结果,而forward则是直接请求

5、属性和监听者

Servlet初始化参数

  • 每个Servlet都可以在DD中配置初始化参数

    <servlet>
       <init-param>
          ...
          <param-name>test</pram-name>
          <param-value>testValue</param-value>
       </init-param>
    </servlet>
    

    获取属性方法如下:getServletConfig().getInitParameter("test")

    这样的好处是每个Servlet在被初始化的时候都能够获取到这个参数,当参数需要修改的时候,直接修改DD即可,不用再重新编译文件

    • getServletConfig()返回的值是初始化Servlet的时候传入的ServeltConfig对象,此对象是在init(ServletConfig config)的时候初始化的,因此不能Servlet构造函数中使用此对象。

      最好不在构造方法中写任何代码,而是init()

    • Servlet的生命周期可以知道,ServletConfig只会被初始化一次,因此当初始化参数被修改后,需要重新启动容器。

上下文初始化参数

上下文初始化参数和Servlet初始化参数比较相像,但是上下文初始化参数是全局的,而且是在Web容器启动时就会初始化的。

DD配置如下

<context-param>
    <param-name>test</param-name>
    <param-value>testValue</param-value>
</context-param>

获取方式如下:

getServletContext().getInitParamter("test");

上下文对象

上下文对象作为全局对象还包括getAttribute()setAttribute(),removeAttribute()方法,这些方法可以用于在全局中传递值。但是由于是全局对象,这些属性的设置,并不是线程安全的

HttpServletRequest也包含此方法,用户在请求跳转的时候,传递值。一般多用于JSP中

ServletContextListener

某些时候,我们需要在上下文初始化的时候,做一些其他的事,比如生成一个DataSource()对象,并放入全局中,比如获取数据库连接等等,这个时候可以使用上下文初始化监听器ServletContextListener,在上下文初始化之后,执行操作。

使用方法如下:

public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //上下文初始化时执行的代码
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //上下文销毁时执行的代码
    }
}

在实现该接口后,在DD中配置监听者即可:

<listener>
    <listener-class>
        com.dengchengchao.servlet.MyServletContextListener
    </listener-class>
</listener>

此时便完成监听。

值得注意的时候,监听器是在上下文对象初始化完成后才执行,因此可以在监听器中使用上下文对象

其他监听者

Servlet中,一共有如下几个监听类型:

场景接口
上下文对象某个属性被改动(增,删,改)的时候发生ServletContextAttributeListener
会话Session对象被创建\销毁时发生HttpSessionListener
有请求Request到来的时候发生ServletRequestEvent
请求Request属性被改动时发生ServletRequestAttributeListener
将某个指定的JavaBean绑定到Session中时发生HttpSessionBindingListener
上下文对象创建\销毁时发生ServletContextListener
某个指定的JavaBean所绑定到的Session在JVM中被迁移时发生HttpSessionActivationListener
会话对象某个属性被改动的时候发生HttpSessionAttributeListener

看起来有8个比较多,但是大多都是重复的,只是所针对的对象不同,总结如下:

  • 上下文对象ServletContext
    • 对象创建:ServletContextListener
    • 属性修改:ServletContextAttributeListener
  • 请求对象Request
    • 对象创建:ServletRequestEvent
    • 属性修改:ServletRequestAttributeListener
  • 会话对象Session
    • 对象创建:HttpSessionListener
    • 属性修改:HttpSessionAttributeListener
  • 属性类对象JavaBean
    • 绑定到会话中:HttpSessionAttributeListener
    • 所绑定的会话被迁移:HttpSessionActivationListener

因此可以发现监听类型就两种,拥有监听器的对象就4个:上下文对象ServletContext,请求对象ServletRequest,会话对象HttpSession,属性类对象JavaBean,可监听的事件为:对象本身的生命周期和对象所拥有的属性的生命周期

比较疑惑的可能是什么是会话属性类对象JavaBean的事件。

这里指的是,当这个对象本身,作为Session的属性Attribute加入到了会话中,则通知这个监听器。它所针对的对象便是被加入的属性。比如当Dog对象被加入下Session对象中,则通知监听者。

SessionAttribute是指所有的属性的改动。当然对这些属性进行筛选同样能够获得上面的功能

什么是属性

属性就是一个对象,可以被设置在ServeltContextHttpServletRequestHttpSession中,从这个被设置的对象来看,他们更像是一个属性的容器(HashMap),可以存储\传递这些属性。所有能访问这些对象的人,都可以访问这个属性。存在不同容器中的属性所具有的生命周期不同。

三大对象的生命周期

  • 上下文ServletContext

    随着Web容器的启动而创建,随着Web容器的关闭而销毁,Web应用中所有地方都能访问

  • 会话属性HttpSession

    随着用户访问时创建Session时而创建,随着Session过期或者手动删除而销毁

  • 请求属性ServletRequest

    随着用户请求到来而创建,随着用户请求完成而销毁

注意属性不是线程安全的

  • 对于上下文对象来说,由于上下文对象是全局的,而所有的请求都是异步的,因此上下文对象的属性最容易引起多线程安全问题,当需要对上下文对象属性操作有线程安全限制的时候,需要对ServletContext加锁
  • 对于会话属性HttpSession来说,通常同一个用户只会访问同一个对象,因此一般不会有线程安全的问题,但是如果用户多线程访问服务时,依然会有线程安全的问题。此时可以对HttpSession加锁
  • 对于请求属性HttpServletRequest来说,此对象随着请求的创建而创建,请求完成而消息,因此它不会有多线程安全的问题

请求的分派

分派相当于这部分请求完成后,需要其他部分继续请求。

RequestDispatcher view = request.getRequestDispatcher("result.jsp");
view.forward(request,response);
//或者
//view.include(request,response);

也可以通过ServletContext获得RequestDispatcher,区别在于ServletContext无法使用相对路径,因此ServletContext必须以/开头

当调用view.forward后,响应就已经提交,也就是isCommited()变为的true,此时不能再修改response中的Header部分响应,否则会报错。

但是后面的其他代码依然会执行

线程安全问题

1上下文属性不是线程安全的,每个Servlet都可以修改ServletContext的内容,如果考虑线程安全一定要使用ServletContext的情况的话,使用:

synchronized(getServletContext()){
	//对上下文的操作
}

但同步就意味着妨碍并发性,开发必须尽量让同步时间最短
2)会话属性(session)也不是线程安全的,一个用户也可能打开两个不同的浏览器,而容器还是以为用户使用相同的会话,用户的多个同时的请求可能会影响安全。
关键是让用户在同一个会话内只能同时发送一个请求,或者将请求同步执行
可以对HttpSession同步:

synchronized(session){
	//对session的操作
}

3)只有请求属性和局部变量是线程安全的
一般来说,不使用对实例变量的同步(sychronized),这一方面影响效率并一方面设计与考虑也很麻烦(如serlvet计数器的例子,不能使用实例池的解决方案,尽管可能实现singleThreadModel也可能事与愿违),所以一个好的servlet不会看到任何实例变量另一方面也要加强的作用域的利用

6、会话管理

会话管理

由于HTTP的请求是无状态的,因此当需要有状态式的请求时,可以有以下几种解决方案:

  • 使用EJB提供的带状态的容器
  • 使用数据库暂时保存用户上个请求的信息
  • HttpSession

对于轻量级服务器来说,一般没有EJB功能,使用数据库又比较复杂,因此基本都是选择的HttpSession

对于同一个客户,HttpSession 对象可以跨多个请求保存会话式的状态,因此对于会话期间客户所做的所有请求,从中得到的所有信息都可以用HttpSession对象保存

深入的理解可以知道:HttpSession跨请求的底层实现应该是通过HashMap<int,HttpSession>来实现,浏览器第一次请求的时候,服务器会生成一个不重复的jsessionId放入cookie中,浏览器每次在请求这个Host的时候,都会带上此cookie,而服务端就可以通过全局的HashMap<int,HttpSession>根据该jsessionId找到对应的HttpSession对象

浏览器中cookie保存是根据Host来保存的,只有在请求对应的Host的时候,才会发送对应的cookie

服务端的Session

  1. Session指的是由服务端维护的会话,一般通过Cookie来实现。

    //向请求获取一个会话
    //如果浏览器是第一次请求服务,则容器会自动生成一个新的会话
    HttpSession session = request.getSession();
    
  2. 由于无论浏览器是不是第一次请求服务,request.getSession()都会返回一个session,因此可以通过

    session.isNew();
    

来判断所获取的session是否是第一次产生。

  1. 某些时候,可能不需要产生一个新的Session,此时可以通过重载方法:

    //向请求获取一个会话
    //如果是浏览器是第一次请求服务,则返回null
    HttpSession session = request.getSession(false);
    

    来只获取新的session

  2. 当客户端不允许使用cookie的时候,Tomcat会使用URL重写来达到Session的功能,此时对所有需要拥有cookie的链接应该使用response.encodeURL("/result")来使Tomcat完成URL重写

    Tomcat是如何辨认用户禁用了Cookie呢?当Tomcat返回用户第一次请求的时候,会同时带上CookieURL重写,当用户第二次返回的时候,如果没有Cookie而有URL重写的时候,则说明用户禁用了Cookie

  3. 重定向的时候,可以使用response.encodeRedirectURL(“”)代替response.sendRedirect()方法

Seesion管理

  1. 可以设置session的有效时间:setMaxInactiveInterval()设置过期时间,单位毫秒。

  2. 可以手动结束session:invalidate()

  3. 其他常用方法:

    • getCreationTime:获取创建此会话的时间
    • getLastAccessedTime:获取此会话的最后一次活跃时间
    • getMaxInactivelInterval:获取此会话的过期时间
  4. 可以在DD中配置默认的超时时间

    <session-config>
       <session-timeout>15</session-timeout>
    </session-config>
    

    注意单位是分钟

Cookie

可以直接创建一个cookie

COokie cookie = new Cookie("username",name);
cookie.setMaxAge(30*60);      //设置过期时间,单位秒。值为-1时浏览器退出时cookie就会消失
response.addCookie(cookie);   //将cookie返回给用户

Cookie[] coopkies = request.getCookies(); //获取用户请求时所带的cookie

CookieHeader比较相似,但是Cookie只有addCookie方法,而Header还有setHeader方法

HeadersetHeader会将key相同的值全部替换

不过HeaderCookie都允许重复add

SessionListener

有关Session的监听器有4个

监听器作用
HttpSessionBindingListener该属性绑定到session中时触发
HttpSessionActivationListener会话在JVM中迁移时触发
HttpSessionListener有会话创建/销毁时触发
HttpSessionAttributeListener当在给会话添加/删除属性的是触发

其中,只有HttpSessionBindingListener不需要在DD中配置

其他的需要在DD中配置

7-8、使用JSP

Servlet作为服务器的小程序,为我们解决了业务问题(作为控制器)。但是,我们想要写出更接近“前端”的代码,需要我们了解JSP相关技术。在JSP中,我们要了解:

JSP代码组成

  • 模板
  • 脚本元素(指令、动作、声明、scriptlet和表达式)
  • 标准动作和定制动作
  • 语法
形式用法备注
scriptlet<% i++; %>
指令<%@ page import=“foo.*,java.util”%>(引入多个包)
表达式代码<%= Counter.getCounter()%>
使用表达式不必加分号
声明<%! int i = 0;%> <%! int doubleCount(){pass}%>
JSP注释<%-- JSP注释 --%>
HTML注释
EL${applicationScope.mail}

从JSP到Servlet

  • 查看指令,转换相应的方法(指令的优先级最高,其次再就近原则)
  • 创建一个HttpServlet的子类
  • 检查声明,把变量写入类
  • 创建服务方法
  • 把HTML转换成流输出的方式

JSP的生命周期

  • 页面转换:JSP->Servlet

  • JSP页面编译

  • 加载类

  • 创建实例

  • jspinit

  • 使用_jspService

  • JSP文件被部署到Web容器里

  • 第一个请求到来,JSP文件被转换成.java

  • Java文件被编译成.class文件

  • 容器加载Servlet类

  • 实例化Servlet,调用servlet的jspInit()初始化

  • 配置初始化参数、

<servlet>
    <jsp-file>somefile</jsp-file>
    <init-param>
        <param-name></param-name>
        <param-value></param-value>
    </init-param>
</servlet>

或在JSP文件中写声明:

<%!
    public void jspInit(){
        // 在这里写初始化的逻辑代码
    }
%>

作用域(PageContext中的枚举值)

  • APPLICATION_SCOPE
  • PAGE_SCOPE
  • REQUEST_SCOPE
  • SESSION_SCOPE

使用指令写JSP代码

  • page

    • import
    • isThreadSafe
    • contentType
    • isErrorPage
    • errorPage
    • pageEncoding
    • 。。。。。。
  • include

  • tablib

  • 这些指令已经允许我们在JSP页面中书写Java代码,但是,为了后期维护,我们不应该写scriptlet。这就需要我们在DD中禁用小脚本

    <web-app>
        <jsp-config>
            <jsp-property-group>
                <!-- 下面的配置对所有的JSP文件,都禁用Java小脚本 -->
                <url-pattern>*.jsp</url-pattern>
                <scripting-invalid>true</scripting-invalid>
            <jsp-property-group>
        </jsp-config>
    <web-app>
    

JSP标准动作

  • 当我们写了太多的脚本,我们开始寻求无脚本页面,于是,JSP标准动作可以来帮忙

  • include(运行时调用-单独的.class文件)

  • forward(跳转JSP、Servlet)

  • JavaBean相关

    • jsp:useBean
    • jsp:setProperty
    • jsp:getProperty
    • jsp:param
  • <%-- 带体的useBean --%>
    <jsp:useBean id="beanId" class="someClass" scope="someScope">
        <jsp:setProperty name="propertyName" value="propertyValue"/>
    </jsp:useBean>
    
    <%-- 使用多态的useBean --%>
    <jsp:useBean id="xx" class="someClass" type="superClassName">
        
    </jsp:useBean>
    
    <%-- 使用param设置bean的属性 --%>
    <jsp:useBean id="xx" class="xx" scope="someScope">
        <jsp:setProperty name="propertyName" param="propertyName"/>
    </jsp:useBean>
    <%-- 如果bean的属性和表单属性名一致,可以使用如下代码 --%>
    <jsp:useBean id="xx" class="xx" scope="someScope">
        <jsp:setProperty name="beanName" property="*"/>
    </jsp:useBean>
    

EL

  • 如果某个JavaBean的属性还是一个对象,我们就得写EL咯!
<%-- dot-operator --%>
${requestScope.name}
<%-- []-operator --%>
${requstScope.someList["0"]}

EL中有一些隐式对象,下面是其一览表:

  • 作用域
    • pageScope
    • requsetScope
    • sessionScope
    • applicationScope
  • 请求参数
    • param
    • paramValue
  • 请求首部
    • header
    • headerValues
  • cookie
  • initParam
  • pageContext
<%-- 下面是一个表单 --%>
<form action="result.jsp" method="post">
    用户名:<input type="text" name="username">
    食物1:<input type="text" name="food">
    食物2:<input type="text" name="food">
</form>
<%--  下面是result.jsp --%>
${param.username}
<%-- 使用paramValues获取多值参数 --%>
${paramValues.food[0]}
<%-- 获取头部信息 --%>
${header.host}

JSP内置对象

  • request
  • response
  • out
  • session
  • config
  • application
  • page
  • pageContext
  • exception

9-10、使用JSTL

当我们需要使用更多的动作,让我们的JSP页面“无脚本化”更好,我们该尝试一下JSTL(JSP标准标签库)。例如,我们可以用下面代码遍历一个对象集合:

<c:forEach var="item" items="{items}">
    ${item}
</c:forEach>

使用下面代码,进行条件判断:

<c:if test="{2 >= 3}">
    <%-- do something --%>
</c:if>

常见的标签有:

  • 核心库(core-c)
    • forEach
    • if
    • choose-when-otherwise
    • set(设置属性值)
    • remove(移除属性值)
    • import(把URL属性增加到页面)
    • param(设置)
    • url(保证URL重写)
    • catch(捕获异常)
<%@ page errorPage="somePage"%>
<%@ taglib uri="" prefix="c"%>
<c:catch var="someException">
    <%-- code --%>
</c:catch>

<c:if test="{someExcpetion != null}">
    ${someException.message}
</c:if>

当我们需要获取更多的功能是,我们可以自定义我们的标签库,具体步骤如下:

  • 编写可以处理业务的Java类
class SomeClass extends SimpleTagSupport{
    @Override
    public void doTag throws JspException, IOException(){
        // 方法体
    }
}
  • 编写标记库描述
<uri></uri>
<function>
    <name></name>
    <function-class></function>
    <function-signature></function-signature>
</function>
  • 使用taglib指令
<%@ taglib prefix="" uri=""%>
  • 使用EL调用函数

11、Web应用部署

  1. WAR(Web ARchive web归档文件)文件是Web应用结构的一个快照,其中文件的放置都是严格按照Servlet规范放置。在建立War包的时候,会将整个Web应用的上下文目录(WEB-INF之上的目录)去除,然后压缩起来,然后给定一个.war后缀

  2. METE-INF/MANIFEST.MF用来声明程序的依赖性的文件,用来防止程序虽然编译通过,但是在运行时却缺少某个依赖。

  3. 设置默认页面:

    当用户访问一个目录文件夹,而没有指定特定的资源的时候:比如:

    http://www.test.com/foo/bar
    

    而不是

    http://www.test.com/foo/index.html
    

    可以在DD中配置默认资源文件,使得容器跳转到最近的资源中国

       <welcome-file-list>
          <welcome-file>index.html</welcome-file>
          <welcome-file>default.jsp</welcome-file>
       </welcome-file-list>
    

    注意,容器只会选择当前目录下的欢迎资源

    比如:

     http://www.test.com/foo/bar
    

    容器只会尝试跳转

     http://www.test.com/foo/bar/index.html
     //或者
     http://www.test.com/foo/bar/default.jsp
    

若这两个文件都没有,则一般会返回404

  1. 在DD中配置错误页面

    当Web应用执行错误时,可以在DD中配置友好错误页面

    • 指定特定异常返回页面
    <error-page>
        <exception-type>java.lang.Throwable</exception-type>
        <location>/errorPage.jsp</location>
    </error-page>
    
    • 指定特定状态码返回页面
      <error-page>
          <error-code>404</error-code>
          <location>/notFoundError.jsp</location>
      </error-page>
    

    可以手动调用HttpServletResponse#sendError()方法告诉容器有错误

    比如:

    response.sendError(HttpServletResponse.SC_FORBIDDEN);
    //等价于
    response.sendError(403);
    
  2. 在DD中配置Servlet初始化

    默认的Servlet会在第一次接受到请求才初始化,因此第一个请求的用户会默认等待Servlet初始化开销,可以通过DD配置初始化顺序:

    <servlet>
        <servlet-name>KathyOne</servlet-name>
        <servlet-class>foo.test</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    

    load-on-startup只要大于0,则表示启动时初始化,而这个值越大,表示初始化优先级越低

  3. 在DD中配置静态资源的ContentType

    Servlet中向某个请求返回文件等其他形式的时候,可以通过response.setContenType()方法设置MIME类型,但是静态资源无法设置ContentType,但是我们可以在DD中配置:

    <mime-mapping> 
       <extension>doc</extension> 
       <mime-type>application/msword</mime-type> 
    </mime-mapping> 
    

    注意不是.doc

    这样浏览器就能够以正确的方式打开此文件。

12、Web应用安全

Servlet 安全验证

Servlet自带了角色验证:

在Tomcat中:

  1. 首先需要在tomcat/conf配置目录下配置tomcat-users.xml
<tomcat-users>
   <role rolename=“Admin”/>
   <role rolename="Member"/>
    <role rolename="Guest"/>
    
    <user username="Dcc" password="admin" roles="Admin,Member,Guest"/>
    <user username="Dc"  password="123"    roles="Member"/>
    <user username="Jack" password="abc"   roles="Guest"/>
</tomcat-users>

大致可以看出来,role元素是用来定义级别的,而user元素便是每个用户

  1. 然后再web.xml中引入在Tomcat中定义的角色

    <security-role>
        <role-name>Admin</role-name>
    </security-role>
    
    <security-role>
        <role-name>Member</role-name>
    </security-role>
    
    <security-role>
        <role-name>Guest</role-name>
    </security-role>
    
    <login-config>
        <auth-method>BASIC</auth-method>
    </login-config>
    

    为什么要再次引入呢?一个Tomcat中可以运行多个应用,为了防止每个应用角色互相干扰,因此可以在web.xml中只引入自己需要的角色

    不要忘了是必须的

  2. 给指定的Servlet配置权限

    <security-constraint>
        <web-resource-collection>
           <web-resource-name>UpdateRecipes</web-resource-name>
           
           <url-pattern>/Beer/AddRecipe/*</url-pattern>
           <url-pattern>/Beer/ReviewRecipe/*</url-pattern>
           
           <http-method>GET</http-method>
           <http-method>POST</http-method>
        </web-resource-collection>
        
        
        <auth-constraint>
            <role-name>Admin</role-name>
            <role-name>Member</role-name>
        </auth-constraint>
    </security-constraint>
    
    • 简单的说,主要配置的元素便是和
    • 一个元素中可以包含多个元素,一个web.xml配置中可以有多个元素
    • 是必须定义的元素
    • 可以为空,为空的时候表示所有方法都受限,有定义的时候表示所定义的方法受限
    • 元素是必须的
    • 当不定以元素的时候,表示此配置不生效,url不受限,当存在 元素而不存在元素的时候,表示所有用户都受限
    • 当一个web.xml中包含多个配置,而每个配置中存在冲突时:只有当某个配置产生了禁止所有人访问的效果是,对应的无论在其他地方如何配置,都无法访问,其他任何时候,都是并集效果
    • 定义的时候,最佳实践为先根据角色分类,再根据方法分类,最后选择对应的
    • 可以在Servlet中使用代码判断授权,request.isUserInRole("xxx")可以判断用户传入的授权,但是本书并不推荐使用它

四大概念

  • 认证
  • 授权
  • 机密性
  • 数据完整

认证

1)四种类型:

类型规范数据完整性注释
BASIC(基本)HTTPBase64-弱HTTP标准,所有浏览器支持
DIGEST(摘要)HTTP强一些,但不是SSL对于HTTP和J2EE是可选的
FORM(表单)J2EE非常弱,没有加密允许有定制的登录屏幕
CLIENT-CERT(客户证书)J2EE强-公共秘钥(PKC)很强,但是用户必须有证书

2)语法
非表单:

<web-app>
	<login-config>
		<auth-method>BASIC</auth-method>          \\or  DIGEST  or  CLIENT-CERT
	</login-config>
</web-app>

表单:

<login-config>
		<auth-method>FORM</auth-method>
		<form-login-config>
		<form-login-page>/loginPage.html</form-login-page>
		<form-error-page>/loginError.html</form-error-page>
		</form-login-config>
</login-config>

在html中:

<form mehtod="POST" action="j_security_check">
	<input type="text" name="j_username">
	<input type="password" name="j_password">
	<input type="submit" value="Enter">
</form>

授权

第一步:定义角色
在开发商的部署文件中修改(如:tomcat-user.xml)

<tomcat-users>
	<role rolename="Admin"/>
	<role rolename="Guest"/>
	<user username="Annie" password="admin" role="Admin,Guest"/>
<tomcat-users>

在web.xml中修改

<security-role><role-name>Admin</role-name></security-role>

第二步:方法约束(对servlet和访问方式这一组合进行约束)

<security-constraint>
	<web-resource-collection>
	<url-pattern>/Beer/AddRecipe/*</url-pattern>
	<url-pattern>/Beer/ReviewRecipe/*</url-pattern>

	<http-method>GET</http-method>
	<http-method>POST</http-method>
	</web-resource-collection>

	<auth-constraint>
		<role-name>Admin<role-name>
		<role-name>Guest<role-name>
	</auth-constraint>
</security-constraint>

在有多种授权定义时:全禁止(有标签、没元素)>>全访问(*)>>允许访问(一个以上元素)

另外如果在程序中使用了isUserInRole(),出于统一性和规范性的考虑,必须需在再DD中再进行映射
例如

\\servlet
	if(request.isUserInRole("Manage")){
\\处理页面
}


\\DD
<servlet>
	<role-name>Manage<role-name>
	<role-link>Admin</role-link>              \\对应部署文件中的角色名
</servlet>

https

当需要数据安全的时候,可以使用HTTPS代替HTTP。在部署文件中:

<security-constraint>
    <web-resource-collection>
       <web-resource-name>UpdateRecipes</web-resource-name>
       
       <url-pattern>/Beer/AddRecipe/*</url-pattern>
       <url-pattern>/Beer/ReviewRecipe/*</url-pattern>
       
       <http-method>GET</http-method>
       <http-method>POST</http-method>
    </web-resource-collection>
    
    
    <auth-constraint>
        <role-name>Admin</role-name>
        <role-name>Member</role-name>
    </auth-constraint>
    
    
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

其中,可以设置为,NONEINTEGRAL(数据无法被更改)或CONFIDENTIAL(数据无法被看见)

并且一般容器都是通过SSL实现安全性保障,因此一般INTEGRAL和CONFIDENTIAL效果是一样的。

配置完成后,当用户第一次请求该资源的时候,会得到一个指向对应HTTPS的地址的301重定向请求,浏览器会重新请求HTTPS地址,此时服务器返回401,浏览器弹窗让用户输入验证,验证成功才返回正确的资源。

13、过滤器与包装器

  • 如果我们需要对用户的请求/服务端响应进行处理,我们就需要使用过滤器。

  • 本质:实现了Filter接口的java类,在DD中单独声明,进行url映射

  • 请求过滤器

    • 安全检查
    • 格式化请求首部
    • 请求审计和日志
  • 响应过滤器

    • 压缩响应流
    • 追加响应流
    • 创建不同的响应流
  1. 过滤器可以链接配置,就像多个过滤网一样

  2. 实现过滤器直接实现Filter接口即可

    具体如下:

    public class LogFilter implements Filter {
    
        private  FilterConfig filterConfig;
    
        //过滤器初始化方法
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
              this.filterConfig=filterConfig;
        }
    
        //过滤方法
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("LogFilter...Begin");
    
            //转发给下层的Servlet/Filter
            filterChain.doFilter(servletRequest,servletResponse);
    
            System.out.println("LogFilter...End");
        }
    
        //过滤器销毁方法
        @Override
        public void destroy() {
    
        }
    }
    

    过滤方法:doFilter包含了请求前和请求后的过滤,在filterChain.doFilter(servletRequest,servletResponse)方法之前的为过滤request,方法之后为过滤response.

    filterChain.doFilter()方法用于将请求转发给下一个过滤链的过滤器。

  3. 理解过滤器的运行路径,可以通过“栈”来理解

    Filter1 -> Filter2 -> Filter3

    其中每个Filter都包含request过滤转发请求,request过滤

    则顺序为Filter1 request过滤->Filter1转发请求->Filter2 request过滤->Filter2 转发请求->Filter3 request过滤->Filter3转发请求->servlet处理->Filter3 response 过滤-> Filter2 response过滤 -> Filter1 response过滤

  4. 在DD中声明过滤器

    <!--声明过滤器-->
    <filter>
        <filter-name>BeerRequest</filter-name>
        <filter-class>com.example.web.BeerRequestFilter</filter-class>
        <init-param>
           <param-name>LogFileName</param-name>
           <param-value>UserLog.txt</param-value>
        </init-param>
    </filter>
    
    <!--声明需要过滤的url-->
    <filter-mapping>
        <filter-name>BeerRequest</filter-name>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>
    
    <!--声明需要过滤的servlet名-->
    <filter-mapping>
        <filter-name>BeerRequest</filter-name>
        <servlet-name>AdviceServlet</servlet-name>
    </filter-mapping>
    

    可以看到,filter比较类似,有name,class,还有param元素.而param则通过Filter中的init(FilterConfig)传入

  5. Servlet 2.4中,过滤器可以应用于请求分派器

    <!--声明需要过滤的url-->
    <filter-mapping>
        <filter-name>BeerRequest</filter-name>
        <url-pattern>*.do</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    

    其中,REQUEST表示对用户请求过滤,若没有配置则默认为REQUEST

    Error表示对错误处理器调用的资源进行过滤

  6. 由前面我们可以知道,某些时候,当响应缓存满了之后,容器会先提交一部分响应给客户端,这样就可能导致我们的过滤器在处理response的时候,response无效。因此如果我们需要在处理response中的流的话,我们需要自己实现一个HttpServletResponse使得响应不会提前提交。

    好消息是Servlet包帮我们进一步完成了这个工作,它提供了4个包装类:

    • ServletRequestWrapper
    • HttpServletRequestWrapper
    • ServletResponseWrapper
    • HttpServletResponseWrapper

    包装类:装饰者模式,使用组合代替继承并能够随时覆盖被包装的类的任意的方法

  7. 使用包装器的方式如下

    //定义想要修改的包装类
    class CompressionResponseWrapper extends HttpServletResponseWrapper{
        @override
        public ServletOutputStream getOutputStream(){
            servletGzipOS = new GzipSOS(resp.getOutputStream());
            return servletGzipOS;
        }   
    }
    
    //在Filter中替换对应的response
    class MyCompressionFilter implements Filter{
       //...
        
        pulic void doFilter(request,response,chain){
            chain.doFilter(request,new CompressionResponseWrapper());
        }
        
      //...
    }
    

    可以看出来,为了是过滤器能够及时处理response,其实就是将response对应的方法替换给servlet使用而已。

14、企业设计模式

  • 软件工程原则
  • 分布式web开发(JNDI RMI)
    • JNDI(Java Naming and Directory Interface):Java命名和目录接口
    • RMI(Remote Method Invocation):远程方法调用
  • MVC进阶(完成控制器的重用)
    为了完成重用,必须松耦合
    新的控制器伪代码:
public class ServletController extends HttpServlet{
  public void doPost(HttpServletRequest request, HttpServletResponse response)
    \\以声明方式调用一个表单验证组件
    \\验证错误也由它处理

    \\以声明方式调用一个请求处理组件
    \\来调用一个模型组件

    \\以声明方式分派到试图JSP
}

这实际上就是Struts!

  • struct学习:
    表单bean:对表单元素定义getter和setter,以及验证错误处理
    Action对象:处理业务逻辑,即使用模型对象。其中主要的方法execute传入表单bean,转发请求给JSP,但具体的对象实在DD中部署,完成松耦合
    struts-config.xml:集成表单bean、Action对象、JSP
    web.xml:配置ActionServlet

    • ServletRequestWrapper
    • HttpServletRequestWrapper
    • ServletResponseWrapper
    • HttpServletResponseWrapper

    包装类:装饰者模式,使用组合代替继承并能够随时覆盖被包装的类的任意的方法

  1. 使用包装器的方式如下

    //定义想要修改的包装类
    class CompressionResponseWrapper extends HttpServletResponseWrapper{
        @override
        public ServletOutputStream getOutputStream(){
            servletGzipOS = new GzipSOS(resp.getOutputStream());
            return servletGzipOS;
        }   
    }
    
    //在Filter中替换对应的response
    class MyCompressionFilter implements Filter{
       //...
        
        pulic void doFilter(request,response,chain){
            chain.doFilter(request,new CompressionResponseWrapper());
        }
        
      //...
    }
    

    可以看出来,为了是过滤器能够及时处理response,其实就是将response对应的方法替换给servlet使用而已。

14、企业设计模式

  • 软件工程原则
  • 分布式web开发(JNDI RMI)
    • JNDI(Java Naming and Directory Interface):Java命名和目录接口
    • RMI(Remote Method Invocation):远程方法调用
  • MVC进阶(完成控制器的重用)
    为了完成重用,必须松耦合
    新的控制器伪代码:
public class ServletController extends HttpServlet{
  public void doPost(HttpServletRequest request, HttpServletResponse response)
    \\以声明方式调用一个表单验证组件
    \\验证错误也由它处理

    \\以声明方式调用一个请求处理组件
    \\来调用一个模型组件

    \\以声明方式分派到试图JSP
}

这实际上就是Struts!

  • struct学习:
    表单bean:对表单元素定义getter和setter,以及验证错误处理
    Action对象:处理业务逻辑,即使用模型对象。其中主要的方法execute传入表单bean,转发请求给JSP,但具体的对象实在DD中部署,完成松耦合
    struts-config.xml:集成表单bean、Action对象、JSP
    web.xml:配置ActionServlet
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值