Head First Servlet && JSP读书笔记

旧城等待, 2022-09-05 06:36 240阅读 0赞

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 {

    1. void init(ServletConfig var1) throws ServletException;
    2. ServletConfig getServletConfig();
    3. void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    4. String getServletInfo();
    5. 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所对应的类即可

    1. <servlet-name>Chapter1 Servlet</servlet-name>
    2. <servlet-class>cn.servlet.Ch1Servlet</servlet-class>

    1. <servlet-name>Chapter1 Servlet</servlet-name>
    2. <url-pattern>/Servlet</url-pattern>

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

  1. <context-param>
  2. <param-name>context/param</param-name>
  3. <param-value>avalible during application</param-value>
  4. </context-param>

mime-mapping

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

  1. <mime-mapping>
  2. <extension>rar</extension>
  3. <mime-type>application/rar</mime-type>
  4. </mime-mapping>
  5. <mime-mapping>
  6. <extension>txt</extension>
  7. <mime-type>applcation/txt</mime-type>
  8. </mime-mapping>

error-page

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

  1. <error-page>
  2. <error-code>404</error-code>
  3. <location>/error.jsp</location>
  4. </error-page>

init-param

作用:配置初始化参数

  1. <servlet>
  2. <servlet-name>chapter2</servlet-name>
  3. <servlet-class>com.example.web.BeerSelect</servlet-class>
  4.   <init-param>
  5.   <param-name>param1</param-name>
  6.   <param-value>avalible in servlet init()</param-value>
  7. </init-param>
  8. <load-on-startup>0</load-on-startup>
  9. </servlet>
  10. <servlet-mapping>
  11. <servlet-name>chapter2</servlet-name>
  12. <url-pattern>/SelectBeer.do</url-pattern>
  13. </servlet-mapping>

welcome-file

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

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

  1. <welcome-file-list>
  2. <welcome-file>aa.html</welcome-file>
  3. <welcome-file>form.html</welcome-file>
  4. </welcome-file-list>

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

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

  1. <servlet>
  2. <servlet-name>chapter2</servlet-name>
  3. <servlet-class>com.example.web.BeerSelect</servlet-class>
  4.   <init-param>
  5.   <param-name>param1</param-name>
  6.   <param-value>avalible in servlet init()</param-value>
  7. </init-param>
  8. <load-on-startup>0</load-on-startup>
  9. </servlet>
  10. <servlet-mapping>
  11. <servlet-name>chapter2</servlet-name>
  12. <url-pattern>/SelectBeer.do</url-pattern>
  13. </servlet-mapping>

4、请求和响应

Servlet

  • Servlet是一种独立于平台和协议的服务器端的技术,可以生成动态的web页面
  • 任务是得到一个用户的请求,再发回一个响应。
  • Servlet生命周期

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

    详细

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

      1. public interface Servlet {
  1. //初始化
  2. void init(ServletConfig var1) throws ServletException;
  3. //处理请求
  4. void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
  5. //销毁
  6. void destroy();
  7. }
  8. 其中`init()``destory()`方法在`Servlet`一生(启动容器至关闭容器)中只会被调用一次。
  9. `service()`方法会在用户请求过程中被调用,且会被多线程调用(**注意线程安全问题**)
  10. 2. 观察`Servlet`包我们可以发现,我们平时并不是直接使用`Servlet`,而是使用它的子类`HttpServlet`
  11. `HttpServlet`的直接父类是`GenericServlet`,为什么会存在3个类呢?
  12. 第一个`Servlet`是接口,这个是毋庸置疑的,但是他们发现`Servlet`中会有许多通用方法,于是写了一个抽象的通用类`GenericServlet`,里面实现了一些通用的方法,由于`Servlet`一开始设计不只是支持`HTTP`协议的,因此当需要其他协议也支持`Servlet`的时候,可以直接继承`GenericServlet`即可,比如`FTPServlet`,但是目前只有`HTTP``Servlet`,所以我们会看到`HttpServlet`
  13. 3. 一般来说,一个容器中,只会存在一个对应的`Servlet`实例,多线程并不是在新线程中新建一个`Servlet`对象,多线程调用的只是`service()`方法
  14. 4. `init(ServletConfig var1)`方法充当的是`Servlet`的构造方法,那为什么要叫`init(ServletConfig var1)`而不直接使用构造方法呢?
  15. 因为`Servlet`是在JDK1.0中发布的,而`JDK1.0`规定**动态加载**的Java类的构造方法是不能带有参数的,因此这里只能使用`init(ServletConfig var1)`代替构造方法
  16. 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请求

    百度 

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

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

    • form表单的method方法指定post请求


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

  1. public void doPost() throws Exception{
  2. doGet(req,resp);
  3. }

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

    1. 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是无效的。

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

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

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

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

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

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

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

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

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

5、属性和监听者

Servlet初始化参数

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

    1. <servlet>
    2. <init-param>
    3. ...
    4. <param-name>test</pram-name>
    5. <param-value>testValue</param-value>
    6. </init-param>
    7. </servlet>

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

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

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

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

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

上下文初始化参数

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

DD配置如下

  1. <context-param>
  2. <param-name>test</param-name>
  3. <param-value>testValue</param-value>
  4. </context-param>

获取方式如下:

  1. getServletContext().getInitParamter("test");

上下文对象

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

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

ServletContextListener

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

使用方法如下:

  1. public class MyServletContextListener implements ServletContextListener {
  2. @Override
  3. public void contextInitialized(ServletContextEvent sce) {
  4. //上下文初始化时执行的代码
  5. }
  6. @Override
  7. public void contextDestroyed(ServletContextEvent sce) {
  8. //上下文销毁时执行的代码
  9. }
  10. }

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

  1. <listener>
  2. <listener-class>
  3. com.dengchengchao.servlet.MyServletContextListener
  4. </listener-class>
  5. </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来说,此对象随着请求的创建而创建,请求完成而消息,因此它不会有多线程安全的问题

请求的分派

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

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

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

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

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

线程安全问题

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

  1. synchronized(getServletContext()){
  2. //对上下文的操作
  3. }

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

  1. synchronizedsession){
  2. //对session的操作
  3. }

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来实现。

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

    1. session.isNew();

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

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

    1. //向请求获取一个会话
    2. //如果是浏览器是第一次请求服务,则返回null
    3. 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中配置默认的超时时间

    1. <session-config>
    2. <session-timeout>15</session-timeout>
    3. </session-config>

    注意单位是分钟

可以直接创建一个cookie

  1. COokie cookie = new Cookie("username",name);
  2. cookie.setMaxAge(30*60); //设置过期时间,单位秒。值为-1时浏览器退出时cookie就会消失
  3. response.addCookie(cookie); //将cookie返回给用户
  4. 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()初始化
  • 配置初始化参数、


    somefile




或在JSP文件中写声明:

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

作用域(PageContext中的枚举值)

  • APPLICATION_SCOPE
  • PAGE_SCOPE
  • REQUEST_SCOPE
  • SESSION_SCOPE

使用指令写JSP代码

  • page

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

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

JSP标准动作

  • 当我们写了太多的脚本,我们开始寻求无脚本页面,于是,JSP标准动作可以来帮忙
  • include(运行时调用-单独的.class文件)
  • forward(跳转JSP、Servlet)
  • JavaBean相关

    • jsp:useBean
    • jsp:setProperty
    • jsp:getProperty
    • jsp:param
  • <%— 带体的useBean —%>

    1. <jsp:setProperty name="propertyName" value="propertyValue"/>

    <%— 使用多态的useBean —%>

    <%— 使用param设置bean的属性 —%>

    1. <jsp:setProperty name="propertyName" param="propertyName"/>


    <%— 如果bean的属性和表单属性名一致,可以使用如下代码 —%>

    1. <jsp:setProperty name="beanName" property="*"/>

EL

  • 如果某个JavaBean的属性还是一个对象,我们就得写EL咯!

    <%— dot-operator —%>
    ${requestScope.name}
    <%— []-operator —%>
    ${requstScope.someList[“0”]}

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

  • 作用域

    • pageScope
    • requsetScope
    • sessionScope
    • applicationScope
  • 请求参数

    • param
    • paramValue
  • 请求首部

    • header
    • headerValues
  • cookie
  • initParam
  • pageContext

    <%— 下面是一个表单 —%>

    1. 用户名:<input type="text" name="username">
    2. 食物1:<input type="text" name="food">
    3. 食物2:<input type="text" name="food">


    <%— 下面是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标准标签库)。例如,我们可以用下面代码遍历一个对象集合:

  1. <c:forEach var="item" items="{items}">
  2. ${item}
  3. </c:forEach>

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

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

常见的标签有:

  • 核心库(core-c)

    • forEach
    • if
    • choose-when-otherwise
    • set(设置属性值)
    • remove(移除属性值)
    • import(把URL属性增加到页面)
    • param(设置)
    • url(保证URL重写)
    • catch(捕获异常)

    <%@ page errorPage=”somePage”%>
    <%@ taglib uri=”” prefix=”c”%>

    1. <%-- code --%>

    1. ${someException.message}

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

  • 编写可以处理业务的Java类

    class SomeClass extends SimpleTagSupport{

    1. @Override
    2. public void doTag throws JspException, IOException(){
    3. // 方法体
    4. }

    }

  • 编写标记库描述





  • 使用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. 设置默认页面:

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

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

    而不是

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

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

    1. <welcome-file-list>
    2. <welcome-file>index.html</welcome-file>
    3. <welcome-file>default.jsp</welcome-file>
    4. </welcome-file-list>

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

    比如:

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

    容器只会尝试跳转

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

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

  1. 在DD中配置错误页面

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

    • 指定特定异常返回页面

      1. <exception-type>java.lang.Throwable</exception-type>
      2. <location>/errorPage.jsp</location>

    • 指定特定状态码返回页面

      1. <error-code>404</error-code>
      2. <location>/notFoundError.jsp</location>

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

    比如:

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

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

    1. <servlet>
    2. <servlet-name>KathyOne</servlet-name>
    3. <servlet-class>foo.test</servlet-class>
    4. <load-on-startup>1</load-on-startup>
    5. </servlet>

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

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

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

    1. <mime-mapping>
    2. <extension>doc</extension>
    3. <mime-type>application/msword</mime-type>
    4. </mime-mapping>

    注意不是.doc

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

12、Web应用安全

Servlet 安全验证

Servlet自带了角色验证:

在Tomcat中:

  1. 首先需要在tomcat/conf配置目录下配置tomcat-users.xml



    1. <role rolename="Guest"/>
    2. <user username="Dcc" password="admin" roles="Admin,Member,Guest"/>
    3. <user username="Dc" password="123" roles="Member"/>
    4. <user username="Jack" password="abc" roles="Guest"/>

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

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

    1. <security-role>
    2. <role-name>Admin</role-name>
    3. </security-role>
    4. <security-role>
    5. <role-name>Member</role-name>
    6. </security-role>
    7. <security-role>
    8. <role-name>Guest</role-name>
    9. </security-role>
    10. <login-config>
    11. <auth-method>BASIC</auth-method>
    12. </login-config>

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

    不要忘了是必须的

  2. 给指定的Servlet配置权限

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

四大概念

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

认证

1)四种类型:




































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

2)语法
非表单:

  1. <web-app>
  2. <login-config>
  3. <auth-method>BASIC</auth-method> \\or DIGEST or CLIENT-CERT
  4. </login-config>
  5. </web-app>

表单:

  1. <login-config>
  2. <auth-method>FORM</auth-method>
  3. <form-login-config>
  4. <form-login-page>/loginPage.html</form-login-page>
  5. <form-error-page>/loginError.html</form-error-page>
  6. </form-login-config>
  7. </login-config>

在html中:

  1. <form mehtod="POST" action="j_security_check">
  2. <input type="text" name="j_username">
  3. <input type="password" name="j_password">
  4. <input type="submit" value="Enter">
  5. </form>

授权

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

  1. <tomcat-users>
  2. <role rolename="Admin"/>
  3. <role rolename="Guest"/>
  4. <user username="Annie" password="admin" role="Admin,Guest"/>
  5. <tomcat-users>

在web.xml中修改

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

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

  1. <security-constraint>
  2. <web-resource-collection>
  3. <url-pattern>/Beer/AddRecipe/*</url-pattern>
  4. <url-pattern>/Beer/ReviewRecipe/*</url-pattern>
  5. <http-method>GET</http-method>
  6. <http-method>POST</http-method>
  7. </web-resource-collection>
  8. <auth-constraint>
  9. <role-name>Admin<role-name>
  10. <role-name>Guest<role-name>
  11. </auth-constraint>
  12. </security-constraint>

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

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

  1. \\servlet
  2. if(request.isUserInRole("Manage")){
  3. \\处理页面
  4. }
  5. \\DD
  6. <servlet>
  7. <role-name>Manage<role-name>
  8. <role-link>Admin</role-link> \\对应部署文件中的角色名
  9. </servlet>

https

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

  1. <security-constraint>
  2. <web-resource-collection>
  3. <web-resource-name>UpdateRecipes</web-resource-name>
  4. <url-pattern>/Beer/AddRecipe/*</url-pattern>
  5. <url-pattern>/Beer/ReviewRecipe/*</url-pattern>
  6. <http-method>GET</http-method>
  7. <http-method>POST</http-method>
  8. </web-resource-collection>
  9. <auth-constraint>
  10. <role-name>Admin</role-name>
  11. <role-name>Member</role-name>
  12. </auth-constraint>
  13. <user-data-constraint>
  14. <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  15. </user-data-constraint>
  16. </security-constraint>

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

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

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

13、过滤器与包装器

  • 如果我们需要对用户的请求/服务端响应进行处理,我们就需要使用过滤器。
  • 本质:实现了Filter接口的java类,在DD中单独声明,进行url映射
  • 请求过滤器

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

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

    具体如下:

    1. public class LogFilter implements Filter {
  1. private FilterConfig filterConfig;
  2. //过滤器初始化方法
  3. @Override
  4. public void init(FilterConfig filterConfig) throws ServletException {
  5. this.filterConfig=filterConfig;
  6. }
  7. //过滤方法
  8. @Override
  9. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  10. System.out.println("LogFilter...Begin");
  11. //转发给下层的Servlet/Filter
  12. filterChain.doFilter(servletRequest,servletResponse);
  13. System.out.println("LogFilter...End");
  14. }
  15. //过滤器销毁方法
  16. @Override
  17. public void destroy() {
  18. }
  19. }
  20. 过滤方法:`doFilter`包含了请求前和请求后的过滤,在`filterChain.doFilter(servletRequest,servletResponse)`方法之前的为过滤`request`,方法之后为过滤`response`.
  21. `filterChain.doFilter()`方法用于将请求转发给下一个过滤链的过滤器。
  1. 理解过滤器的运行路径,可以通过“栈”来理解

    Filter1 -> Filter2 -> Filter3

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

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

  2. 在DD中声明过滤器

    1. <!--声明过滤器-->
    2. <filter>
    3. <filter-name>BeerRequest</filter-name>
    4. <filter-class>com.example.web.BeerRequestFilter</filter-class>
    5. <init-param>
    6. <param-name>LogFileName</param-name>
    7. <param-value>UserLog.txt</param-value>
    8. </init-param>
    9. </filter>
    10. <!--声明需要过滤的url-->
    11. <filter-mapping>
    12. <filter-name>BeerRequest</filter-name>
    13. <url-pattern>*.do</url-pattern>
    14. </filter-mapping>
    15. <!--声明需要过滤的servlet名-->
    16. <filter-mapping>
    17. <filter-name>BeerRequest</filter-name>
    18. <servlet-name>AdviceServlet</servlet-name>
    19. </filter-mapping>

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

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

    1. <!--声明需要过滤的url-->
    2. <filter-mapping>
    3. <filter-name>BeerRequest</filter-name>
    4. <url-pattern>*.do</url-pattern>
    5. <dispatcher>REQUEST</dispatcher>
    6. <dispatcher>INCLUDE</dispatcher>
    7. <dispatcher>FORWARD</dispatcher>
    8. <dispatcher>ERROR</dispatcher>
    9. </filter-mapping>

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

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

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

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

    • ServletRequestWrapper
    • HttpServletRequestWrapper
    • ServletResponseWrapper
    • HttpServletResponseWrapper

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

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

    1. //定义想要修改的包装类
    2. class CompressionResponseWrapper extends HttpServletResponseWrapper{
    3. @override
    4. public ServletOutputStream getOutputStream(){
    5. servletGzipOS = new GzipSOS(resp.getOutputStream());
    6. return servletGzipOS;
    7. }
    8. }
    9. //在Filter中替换对应的response
    10. class MyCompressionFilter implements Filter{
    11. //...
    12. pulic void doFilter(request,response,chain){
    13. chain.doFilter(request,new CompressionResponseWrapper());
    14. }
    15. //...
    16. }

    可以看出来,为了是过滤器能够及时处理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)

    1. \\以声明方式调用一个表单验证组件
    2. \\验证错误也由它处理
    3. \\以声明方式调用一个请求处理组件
    4. \\来调用一个模型组件
    5. \\以声明方式分派到试图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. 使用包装器的方式如下

    1. //定义想要修改的包装类
    2. class CompressionResponseWrapper extends HttpServletResponseWrapper{
    3. @override
    4. public ServletOutputStream getOutputStream(){
    5. servletGzipOS = new GzipSOS(resp.getOutputStream());
    6. return servletGzipOS;
    7. }
    8. }
    9. //在Filter中替换对应的response
    10. class MyCompressionFilter implements Filter{
    11. //...
    12. pulic void doFilter(request,response,chain){
    13. chain.doFilter(request,new CompressionResponseWrapper());
    14. }
    15. //...
    16. }

    可以看出来,为了是过滤器能够及时处理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)

    1. \\以声明方式调用一个表单验证组件
    2. \\验证错误也由它处理
    3. \\以声明方式调用一个请求处理组件
    4. \\来调用一个模型组件
    5. \\以声明方式分派到试图JSP

    }

这实际上就是Struts!

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

发表评论

表情:
评论列表 (有 0 条评论,240人围观)

还没有评论,来说两句吧...

相关阅读