Servlet线程安全、JSP引入

Java打卡:第101天

javaWeb — Servlet和JSP

Java EE


Servlet收尾和JSP


准备参加外包比赛了,可能不会这么频繁了

昨天的内容因为不想篇幅太长,毕竟看的最多的是自己,已经分享了Servlet的请求、响应还有资源的通信方式【请求转发getRequestdispatcher和重定向sendRedirect

Servlet

重定向的乱码问题需要使用工具类URLencoder和decoder解决

String user = request.getParameter("user");
user = new String(user.getBytes("ISO8859-1"),"UTF-8");
//TCP传输的是字节流,所以这里需要转化为字节流,使用工具类URLDecoder
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"); //只是为了TCP的通信
		
		
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"); //不设置就按照普通text解析,并且中文乱码
		
		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);//这里POST提交的时候也会执行GET中的方法
	}

}

@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给出的响应是后面的资源,前面流未开启,输出响应无效

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Umtu9PUb-1640184054019)(D:\安装包\请求转发.png)]

再看一下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应用路径

路径举例

前台路径
//可以先看一下浏览器解析的路径就是前台路径 ; 在Test项目中index.html的表单中

//按照以/开头的方式写
<body>
	<form action="/Test/TestServlet" method="post">
		用户名<input type="text" name="user"/><br>
		年龄&nbsp;&nbsp;<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 {
		//Servlet是单例多线程的,有一个可修改的成员变量user,所以存在线程安全问题
		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);//这里POST提交的时候也会执行GET中的方法
	}

}

这里有一个动态的变量user可修改,存在线程安全

当多个浏览器进行访问,点击server的debug模式就可以进行调试,发现输出的结果不一致

Servlet线程安全问题的解决方案

  • 使用局部变量而不是成员变量
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
		//Servlet是单例多线程的,有一个可修改的成员变量user,所以存在线程安全问题
		String user = request.getParameter("user"); //局部变量在方法栈帧中
		//输出到浏览器
		PrintWriter out = response.getWriter();
		out.append("user = " + user);
	}

局部变量不存在线程安全问题,因为一个线程对应一个栈帧;数据不共享

  • 加上对象锁synchronized
	private String user;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//Servlet是单例多线程的,有一个可修改的成员变量user,所以存在线程安全问题
		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文件即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值