Head First Servlet && JSP
引子
- 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编写
Servlet和
CGI都是在Web服务器中扮演辅助应用的角色,通过
Servlet和
CGI`,能够使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
,Url
和Servlet
所对应的类是分开配置的,通过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项目部署规范
约定大于配置
简单总结如下:
-
在Tomcat容器中,所需要部署的服务,应该放在Tomcat -> webapps目录下
-
用户无法访问
WEB-INF
文件夹以及文件下的任何东西,换句话说,用户可以直接访问项目中除WEB-INF
目录下的任何文件 -
WEB-INF
目录下一般分3个文件
classes
文件夹:Servlet
逻辑处理class文件夹lib
文件夹:classes
文件夹中所依赖的其他文件web.xml
:DD,(Deployment Descriptor)部署描述文件
Tomcat目录结构
- 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 设定的参数名称的值
初始化过程:
-
- 在启动Web项目时,容器(比如Tomcat)会读web.xml配置文件中的两个节点和。
- 接着容器会创建一个ServletContext(上下文),应用范围内即整个WEB项目都能使用这个上下文。
- 接着容器会将读取到转化为键值对,并交给ServletContext。
- 容器创建中的类实例,即创建监听(备注:listener定义的类可以是自定义的类但必须需要继承ServletContextListener)。
- 在监听的类中会有一个contextInitialized(ServletContextEvent event)初始化方法,在这个方法中可以通过event.getServletContext().getInitParameter(“contextConfigLocation”) 来得到context-param 设定的值。在这个类中还必须有一个contextDestroyed(ServletContextEvent event) 销毁方法.用于关闭应用前释放资源,比如说数据库连接的关闭。
- 得到这个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类
- 容器创建两个对象
HttpServletResponse
和HttpServletRequest
- 容器找到对应的
Servlet
类,若该类未被实例化,则实例化之 - 调用对应
Servlet
类的service()
方法,并将HttpServletResponse
和HttpServletRequest
传递给service()
方法 service()
方法完成逻辑调用,并将相应消息通过响应对象返回客户service()
方法结束,HttpServletResponse
和HttpServletRequest
销毁,Servlet
归还线程
详细
-
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()
方法会在用户请求过程中被调用,且会被多线程调用(注意线程安全问题) -
观察
Servlet
包我们可以发现,我们平时并不是直接使用Servlet
,而是使用它的子类HttpServlet
而
HttpServlet
的直接父类是GenericServlet
,为什么会存在3个类呢?第一个
Servlet
是接口,这个是毋庸置疑的,但是他们发现Servlet
中会有许多通用方法,于是写了一个抽象的通用类GenericServlet
,里面实现了一些通用的方法,由于Servlet
一开始设计不只是支持HTTP
协议的,因此当需要其他协议也支持Servlet
的时候,可以直接继承GenericServlet
即可,比如FTPServlet
,但是目前只有HTTP
的Servlet
,所以我们会看到HttpServlet
。 -
一般来说,一个容器中,只会存在一个对应的
Servlet
实例,多线程并不是在新线程中新建一个Servlet
对象,多线程调用的只是service()
方法 -
init(ServletConfig var1)
方法充当的是Servlet
的构造方法,那为什么要叫init(ServletConfig var1)
而不直接使用构造方法呢?因为
Servlet
是在JDK1.0中发布的,而JDK1.0
规定动态加载的Java类的构造方法是不能带有参数的,因此这里只能使用init(ServletConfig var1)
代替构造方法 -
由于
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()
永远都是返回相同的值:用户所知道的端口
-
getParameter
返回值是一个String
类型,因此当一个参数被多次赋值的时候,需要使用getParameterValues
,得到String[]
类型。比如:localhost:8080?name=xxx&&name=xxx
此时可以使用
getParameterValues("name")
获取所有的值,也可以使用getParamter("name")
获取第一个name
参数getParamter()
内部是通过getParameterValues("name")[0]
实现,因此可以混用 -
getIntHeader
等价于Integer.parseInt(getHeader())
-
HttpServletRequest
包含getReader()
和getInputStream()
方法,区别在于一个是字符流,一个是字节流 -
一般来说,
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
的条件的时候,缓冲会首先将status
和headers
写入socket
中。因此对于我们来说,我们最好首先设置
status
和header
,再设置其他的东西,否则如果在缓冲已经写入后再设置status
和header
是无效的。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的区别在于
forward
和include
会有两次与request
和response
交互,而redirect
只有一次 include
和forward
的区别在于include
转发后会再次返回到servlet
,而forward
不会,include
相当于包含了另外一个servlet的结果,而forward
则是直接请求
- redirect 和 include、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
对象中,则通知监听者。
而Session
的Attribute
是指所有的属性的改动。当然对这些属性进行筛选同样能够获得上面的功能
什么是属性
属性就是一个对象,可以被设置在ServeltContext
、HttpServletRequest
、HttpSession
中,从这个被设置的对象来看,他们更像是一个属性的容器(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
-
Session指的是由服务端维护的会话,一般通过
Cookie
来实现。//向请求获取一个会话 //如果浏览器是第一次请求服务,则容器会自动生成一个新的会话 HttpSession session = request.getSession();
-
由于无论浏览器是不是第一次请求服务,
request.getSession()
都会返回一个session
,因此可以通过session.isNew();
来判断所获取的session
是否是第一次产生。
-
某些时候,可能不需要产生一个新的
Session
,此时可以通过重载方法://向请求获取一个会话 //如果是浏览器是第一次请求服务,则返回null HttpSession session = request.getSession(false);
来只获取新的
session
-
当客户端不允许使用
cookie
的时候,Tomcat
会使用URL重写来达到Session
的功能,此时对所有需要拥有cookie
的链接应该使用response.encodeURL("/result")
来使Tomcat
完成URL重写Tomcat
是如何辨认用户禁用了Cookie
呢?当Tomcat
返回用户第一次请求的时候,会同时带上Cookie
和URL重写
,当用户第二次返回的时候,如果没有Cookie
而有URL重写
的时候,则说明用户禁用了Cookie
-
重定向的时候,可以使用
response.encodeRedirectURL(“”)
代替response.sendRedirect()
方法
Seesion管理
-
可以设置
session
的有效时间:setMaxInactiveInterval()
设置过期时间,单位毫秒。 -
可以手动结束
session
:invalidate()
-
其他常用方法:
getCreationTime
:获取创建此会话的时间getLastAccessedTime
:获取此会话的最后一次活跃时间getMaxInactivelInterval
:获取此会话的过期时间
-
可以在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
Cookie
和Header
比较相似,但是Cookie
只有addCookie
方法,而Header
还有setHeader
方法
Header
的setHeader
会将key
相同的值全部替换不过
Header
和Cookie
都允许重复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应用部署
-
WAR(Web ARchive web归档文件)文件是Web应用结构的一个快照,其中文件的放置都是严格按照Servlet规范放置。在建立War包的时候,会将整个Web应用的上下文目录(WEB-INF之上的目录)去除,然后压缩起来,然后给定一个.war后缀
-
METE-INF/MANIFEST.MF
用来声明程序的依赖性的文件,用来防止程序虽然编译通过,但是在运行时却缺少某个依赖。 -
设置默认页面:
当用户访问一个目录文件夹,而没有指定特定的资源的时候:比如:
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
-
在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);
-
在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,则表示启动时初始化,而这个值越大,表示初始化优先级越低 -
在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中:
- 首先需要在
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
元素便是每个用户
-
然后再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中只引入自己需要的角色
不要忘了是必须的
-
给指定的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(基本) | HTTP | Base64-弱 | 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>
其中,可以设置为,NONE,INTEGRAL(数据无法被更改)或CONFIDENTIAL(数据无法被看见)
并且一般容器都是通过SSL实现安全性保障,因此一般INTEGRAL和CONFIDENTIAL效果是一样的。
配置完成后,当用户第一次请求该资源的时候,会得到一个指向对应HTTPS的地址的301重定向请求,浏览器会重新请求HTTPS地址,此时服务器返回401,浏览器弹窗让用户输入验证,验证成功才返回正确的资源。
13、过滤器与包装器
-
如果我们需要对用户的请求/服务端响应进行处理,我们就需要使用过滤器。
-
本质:实现了Filter接口的java类,在DD中单独声明,进行url映射
-
请求过滤器
- 安全检查
- 格式化请求首部
- 请求审计和日志
-
响应过滤器
- 压缩响应流
- 追加响应流
- 创建不同的响应流
-
过滤器可以链接配置,就像多个过滤网一样
-
实现过滤器直接实现
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()
方法用于将请求转发给下一个过滤链的过滤器。 -
理解过滤器的运行路径,可以通过“栈”来理解
Filter1 -> Filter2 -> Filter3
其中每个Filter都包含request过滤,转发请求,request过滤
则顺序为Filter1 request过滤->Filter1转发请求->Filter2 request过滤->Filter2 转发请求->Filter3 request过滤->Filter3转发请求->servlet处理->Filter3 response 过滤-> Filter2 response过滤 -> Filter1 response过滤
-
在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)
传入 -
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表示对错误处理器调用的资源进行过滤
-
由前面我们可以知道,某些时候,当响应缓存满了之后,容器会先提交一部分响应给客户端,这样就可能导致我们的过滤器在处理response的时候,response无效。因此如果我们需要在处理response中的流的话,我们需要自己实现一个
HttpServletResponse
使得响应不会提前提交。好消息是Servlet包帮我们进一步完成了这个工作,它提供了4个包装类:
ServletRequestWrapper
HttpServletRequestWrapper
ServletResponseWrapper
HttpServletResponseWrapper
包装类:装饰者模式,使用组合代替继承并能够随时覆盖被包装的类的任意的方法
-
使用包装器的方式如下
//定义想要修改的包装类 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:配置ActionServletServletRequestWrapper
HttpServletRequestWrapper
ServletResponseWrapper
HttpServletResponseWrapper
包装类:装饰者模式,使用组合代替继承并能够随时覆盖被包装的类的任意的方法
-
使用包装器的方式如下
//定义想要修改的包装类 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