Servlet收尾和JSP
准备参加外包比赛了,可能不会这么频繁了
昨天的内容因为不想篇幅太长,毕竟看的最多的是自己,已经分享了Servlet的请求、响应还有资源的通信方式【请求转发getRequestdispatcher和重定向sendRedirect
Servlet
重定向的乱码问题需要使用工具类URLencoder和decoder解决
String user = request.getParameter("user");
user = new String(user.getBytes("ISO8859-1"),"UTF-8");
System.out.println(request.getServerName() + " : " + user);
URLEncoder.encode(user, "UTF-8");
System.out.println(user);
response.sendRedirect("some?user=" + user);
String user = request.getParameter("user");
URLDecoder.decode(user, "UTF-8");
user = new String(user.getBytes("ISO8859-1"), "UTF-8");
System.out.println("user = " + user);
重定向可跳转到其他application
请求转发只能在本项目的资源中跳转,而重定向可以跳转到其他的应用的资源
比如这里的someServlet在项目proj-2下面
注意跳转到其他应用的写法要加上应用名和资源名,还要加上/
response.sendRedirect("/proj-2/some")
之前的一个应用跳转不需要加/,因为只会在本应用中寻找,但是这里的跳转到其他应用,就是在服务器中寻找,所以加上/,并且加上后面的资源 这样便可以跳转
重定向和请求转发的choose
-
请求转发 :
- 浏览器只是发出一次请求,收到一次响应【键入的URL访问资源的request和response】
- 请求所转发的资源可以实现数据共享
- 浏览器地址栏显示为提交请求的路径 【include虽然是最后一个资源的流才开启,但是响应对象还是第一个资源的,所以地址栏还是请求路径】
- 只能在当前应用中跳转
-
重定向
- 浏览器发出多次请求,多次响应;【可用抓包工具监测】因为HTTP为无状态协议,前后的请求没有关系,默认GET
- 重定向到的资源不能共享数据【要想进行数据传输只能使用?name=value】
- 浏览器地址栏显示的为重定向的请求路径,不是用户提交的路径,重定向的作用就是:
防止表单重复提交
; 这里可能会有恶意提交【就是不断刷新表单提交;这样服务器大量运算宕机】 但是重定向的显示的路径是重定向请求路径,和之前的表单请求路径不相同,所以刷新也不能恶意提交 - 重定向不仅可以跳转到当前应用的其它资源,也可以跳转到其他应用的资源,前者不需要/,后者需要完整的路径
-
重定向和请求转发的选择
- 需要跳转到其他的应用,这个时候要使用重定向
- 如果是处理表单数据的Servlet跳转到其他的Servlet,为防止恶意提交,使用重定向
- 若对某一请求进行处理的Servlet需要消耗大量的服务器资源,为了防止恶意刷新,使用重定向
- 也就是说除了特殊的只能用请求转发,比如要共享域属性,其他的如果都可以使用的时候,使用重定向
RequestDispatcher
之前看到的forward方法将request和response直接传递给下一个资源,那么者两个request的类型,真的相同吗?
System.out.println("request1 = " + request);
System.out.println("response1 = " + response);
request.getRequestDispatcher("/some").forward(request, response);
System.out.println("request2 = " + request);
System.out.println("response2 = " + response);
request1 = org.apache.catalina.connector.RequestFacade@5921c5e7
response1 = org.apache.catalina.connector.ResponseFacade@7e10a24c
request2 = org.apache.catalina.core.ApplicationHttpRequest@4177e2bd
response2 = org.apache.catalina.connector.ResponseFacade@7e10a24c
这下可以看到响应是完全一模一样的,但是请求却有差别,第一个请求就是一个RequestFacade对象;而第二个是ApplicationHttpRequest ---- 这里使用装饰器模式,对请求进行了增强;因为其实资源1到资源2也是一种请求,这里就是合并了请求
Dispatcher还有一个方法是include,看一下include的效果,只是简单替换
System.out.println("request1 = " + request);
System.out.println("response1 = " + response);
request.getRequestDispatcher("/some").include(request, response);
System.out.println("request2 = " + request);
System.out.println("response2 = " + response);
request1 = org.apache.catalina.connector.RequestFacade@1322f91f
response1 = org.apache.catalina.connector.ResponseFacade@37af6674
request2 = org.apache.catalina.core.ApplicationHttpRequest@3d25da26
response2 = org.apache.catalina.core.ApplicationHttpResponse@44169dff
可以看到,请求还是进行了装饰增强,将请求进行了合并,include的不同的地方是资源2的响应也发生了变化,后者又进行了增强
forward资源1的输出无
之前使用forward进行请求转发的时候,就发现只有资源2的响应,资源1的响应没了
但是换成include就发现资源1的也在
@WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" })
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<font color='green'>你好,这里是cfeng.com</font>");
request.getRequestDispatcher("/some").include(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
@WebServlet(description = "just for testing", urlPatterns = { "/some" })
public class someServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("request2 = " + request);
System.out.println("response2 = " + response);
PrintWriter out = response.getWriter();
out.println("<font color='red'>你好,java!</font>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
你好,这里是cfeng.com 你好,java!
forward
: 向前,代表请求还没有结束,请求还要继续向前,请求要结束之后才能又响应,这个时候请求没有结束,所以还没有响应【但是响应对象存在,但是没有连接到客户端,没有开启标准输出流writer,只是不能发挥功能】
include
:包含,把另外一个资源的数据给包含到这个的标准输出流中;资源1的流就已经开启了
两者的区别主要就是标准输出流的开启时间,forward的请求继续向前【标准输出流会连接到客户端】,所以服务器不会再这里打开标准输出流;所以此时写入到out中的数据是不会写入到客户端浏览器的
但是include意为包含,表示当前请求”结束“,可以对客户端进行相应了,可以连接,就可以开启输出流了,自己的数据可以写入到标准输入流中,还会将其他的数据写入流
forward和include区别是流开启时间不同
主要区别就是流的开启时间不同,还有就是给出客户端响应的资源不同【因为一共就是一次响应,一次请求】
forward是资源2时才会连接客户端,开启输出流,所以真正的响应式资源2给出的; include是将后者包含,所以资源1的时候就会连接客户端了,开启输出流,响应是资源1给出的,资源2的响应包含再资源1中
forward给出的响应是后面的资源,前面流未开启,输出响应无效
再看一下include的转发方式
其实就是访问资源的路径不同,forward是形成一个闭环,就是单线的,给出响应的是最后的一个资源;include的转发方式就是重复走线路,给出的响应是第一个资源,所以后面的响应都是装饰增强过的【include包含后面的响应】
所以如果使用forward进行转发,那么使用forward的servlet就不能向response中写入数据,如果要写入数据,就要使用include进行转发
需要注意的是,一共还是一个请求,只是后面的资源会对这个请求或者响应进行装饰
访问路径
URL,统一资源定位符,用来定位资源的一种方式。 通常URL资源访问路径由两部分组成 : 资源路径和资源名称
资源名称是要访问资源的直接名称,比如index.html;或者域资源存在映射关系的间接名称【比如servlet】
资源路径是通过该路径可以定位到指定的资源,即资源路径是指URL路径中除了资源名称以外的部分
一般情况下,再URL或者URI中,最后一个斜杠/后面就是资源名称,前面的就是资源路径
http://localhost:8080/Test/TestServlet
http://localhost:8080/Test 资源路径
TestServlet 资源名称
根据资源路径是否可以独立完成资源的定位,可以将访问路径分为绝对路径和相对路径 在浏览器中,要想定位,必须加上协议,主机,端口
绝对路径
绝对路径,是指根据给出的访问路径可以精确找到资源的路径;比如,你告诉别人地址: 你说我在NPU的校门口;这就是一个绝对的地址,别人就可以用地图导航找到 ;对于web应用而言,是指带访问协议的路径,就是URL,比如http://localhost:8080/Test/TestServlet ----- 【URL】
相对路径
相对路径,是指仅根据访问路径无法精确定位资源的路径; 相对路径必须结合参照路径才能找到资源,参照路径不同,形成的绝对路径就不同。 绝对路径 = 相对路径 + 参照路径
比如你告诉别人地址,我在学校的校门口这里; 然后不同学校的学生会默认自己的学校为相对路径,所以得到的绝对路径不容,比如NPU,那么绝对路径就是NPU的校门口
web应用中,浏览器或者服务器自动为不同的相对路径添加不同的参照路径,将相对路径转化为绝对路径;所以最关键的是了解程序添加参照路径的规则
相对路径的写法有两种,一种以/开头; 一种以路径名称开头,行对路径是否以/开头,路径出现的文件不同,默认的参照路径是不同的;比如forward和include添加的方式是不同的,一个有/,一个直接以资源名称开头
以/开头的相对路径
以/开头的相对路径,根据路径所在文件所处位置不同,分为前台路径和后台路径
前台路径 : 浏览器
所解析执行的代码中所包含的路径。比如html,css,js中的路径,jsp文件的路径;比如html或者jsp文件中的< a>中href的路径,还有form中的action路径,< link>中的路径都是前台路径 前台路径的参照路径是服务器的根路径:也就是http://127.0.0.1:8080 前台路径转化为绝对路径是浏览器自动完成,作用是用户对资源请求,前台路径的作用是”查找“
后台路径 : 服务器
后台解析执行的代码文件中的路径,比如java代码【比如forward】中的路径、xml文件中的路径,后台路径的参照路径是web应用的根路径,比前台多一个web应用,比如http://127.0.0.1:8080/app,这种转换服务器自动完成,路径的作用是标识在服务器中的路径,方便客户端查找资源,作用为”标识“
后台路径的特例: 当使用sendRedirect方法进行重定向的时候,如果使用/开头,那么参照路径不是web应用的路径,而是整个web服务器的路径,也就是前台路径处理了;因为重定向的时候可以定位到其他的应用中
以资源名称开头的路径
以路径名称开头的路径,不管出现在前台还是后台,参照路径都是当前访问路径的资源路径;比如response的重定向中,定位的就是当前的资源路径,其实就是web应用路径
路径举例
前台路径
<body>
<form action="/Test/TestServlet" method="post">
用户名<input type="text" name="user"/><br>
年龄 <input type="text"/ name="age"><br>
爱好:<br>
<input type="checkbox" name="hobby" value="soccer"/>足球
<input type="checkbox" name="hobby" value="basketball"/>篮球
<input type="checkbox" name="hobby" value="tennis"/>网球
<br>
<input type="submit" value="注册"/>
</form>
</body>
可以看到是/Test/TestServlet 这里是前台路径,所以参照的是web服务器的路径,也就是http://127.0.0.1:8080,加上之后可以定位;如果是/TestServlet 那加上就是http://127.0.0.1:8080/TestServlet ;但是服务器的目录webapp中只有各个项目的名称,没有TestServlet这个应用,所以访问失败
<form action="TestServlet" method="post">
这里是资源开头,以当前URL的资源路径为相对路径,因为进入应用之后,URL是http://127.0.0.1:8080/Test/index.html ,资源路径就是web应用的路径,也就是http://127.0.0.1:8080/Test;加上就可以定位,当前资源就是指的是放路径这个资源的路径,比如servlet
其实就是一级一级查找是否有目录,如果不是直接在【项目中src下面的webapp就是当前web应用的根】
后台路径
后台路径就是服务器解析的文件中的路径;比如web.xml中的路径就是后台路径
<url-pattern>/some</url-pattern>
这里服务器给的参照路径就是web应用的路径http://127.0.0.1:8080/Test;加上/some;形成的绝对路径可以找到资源
request.getRequestDispatcher("/some").forward(request, response);
这里出现在java代码中,是后台路径,加上/默认的参照路径就是当前web应用的路径http://127.0.0.1:8080/Test
如果去掉/,就是资源名称开头的路径,那么这里的就是当前资源的URL的资源路径,URL是http://127.0.0.1:8080/Test/TestServlet ;资源路径就是前面的,和上面相同,所以可以去掉/
后台路径特例
后台路径的特例就是Servlet的重定向,重定向的参照路径实际上是“web服务器的根路径”,而不是一般后台路径的参照web项目的路径
response.sendRedirect("/Test/some");
前面的项目的名称是可以动态获取的,使用request方法
response.sendRedirect(request.getContextPath() + "/some");
这样子才可以正常访问,必须加上项目的名称,因为参照路径只有前面的web服务器的路径; 之所以为特例,是因为重定向方法是不仅跳转到当前项目下面,也可以跳转到其他的项目下面
只有这里的Servlet的Response的重定向是特例,其他的后台的重定向就是遵循规则的
路径名称开头
主要就是看当前资源的URL的资源路径;以该资源路径为相对路径,所以要合理使用,比如上面的特例后台路径,就可以使用资源路径
response.sendRedirect("some");
当前的资源路径中就包含项目名称,所以就可以正常使用
因此,
当加上/或者不加/都可以完成跳转,那么就要加上/,因为不加/相对路径随资源变化而变化,但是加上就不会变化
Servlet线程安全
Servlet是单例多线程,这在之前证明过,对象只会创建一次,每次访问就是调用service方法;每一个客户端可以同时访问,所以是多线程的;
出现线程安全问题的条件
当多个线程修改同一个共享数据的时候,后修改的数据将先修改的数据覆盖,对数据先进行修改的用户读取的用户读取到的不是自己修改的数据----所以出现了线程安全问题 【之前收集】
JVM线程安全问题
栈内存数据分析
栈中放的是方法的栈帧,栈是多例的,JVM会给每一个线程创建一个栈,所以数据不共享,方法中的局部变量是存放在栈的栈帧中,一个方法就一个方法栈帧,方法执行完毕之后,栈帧弹栈,局部变量消失,局部变量是局部的,不共享,所以栈不存在线程安全问题
堆内存数据分析
一个JVM只存在一个堆内存,堆内存是共享的,被创建的对象是存放在堆内存中,堆内存的数据是多线程共享的,所以存在线程安全问题【new的对象,数组】
方法区的数据分析
一个JVM中只存在一个方法区。静态变量和常量存放在方法区,方法区是多线程共享的,常量是不可以修改的;常量是不能被修改的,不存在线程安全问题。静态变量是多线程共享,存在安全问题
线程安全问题方案【important】
- 对一般性的类,不要定义为单例的,除非项目有特殊需求,或该类对象属于重量级对象,—也就是创建对象需要占用大量的资源,比如数据库连接对象
- 无论类是否单例,则该类中尽量不使用静态变量【静态变量在方法区中,单例共享】
- 若需要定义为单例类,则该单例类精良不使用成员变量
- 若该类中必须使用成员变量,则对成员变量的操作,可以添加串行化锁synchronized,实现线程同步;但是使用线程同步机制,会出现串行化排队,影响执行效果,并且操作不当会死锁
Servlet线程安全
Servlet是单例多线程的,可能存在线程安全问题,如果存在可修改的共享数据,那么就出现线程安全问题
public class TestServlet extends HttpServlet {
private String user;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
user = request.getParameter("user");
PrintWriter out = response.getWriter();
out.append("user = " + user);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
这里有一个动态的变量user可修改,存在线程安全
当多个浏览器进行访问,点击server的debug模式就可以进行调试,发现输出的结果不一致
Servlet线程安全问题的解决方案
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String user = request.getParameter("user");
PrintWriter out = response.getWriter();
out.append("user = " + user);
}
局部变量不存在线程安全问题,因为一个线程对应一个栈帧;数据不共享
private String user;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
synchronized (this) {
user = request.getParameter("user");
PrintWriter out = response.getWriter();
out.append("user = " + user);
}
}
这里加上对象锁,那么就会进行锁等待,一个执行完之后,另外一个才会执行
线程安全问题的合理利用
这里线程问题使用线程安全问题做一个简单的计数器,统计访问次数
public class TestServlet extends HttpServlet {
private int count;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
count++;
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.append("该页面被浏览了" + count + "次");
}
这里就不需要考虑数据不一样的问题了,因为本来就不要求修改后的数据还是之前修改的数据
JSP
JSP,java server page,java的服务器界面,运行在服务器端的页面。是SUN公司倡导建立的一种动态网页技术。JSP就是在传统的静态网页HTML文件中插入java代码片段和JSP标签形成的一种文件。后缀名为jsp。使用JSP开发的web应用是跨平台的,既能够在Linux上运行,也能在其他的平台上运行【和java一样具有跨平台性】
创建jsp文件在eclipse中就直接使用new JSP文件即可