总结
一、乐观锁和悲观锁
在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。
典型的冲突有:
l 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。
l 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6。
为了解决这些并发带来的问题。 我们需要引入并发控制机制。
并发控制机制
最常用的处理多用户并发访问的方法是加锁。当一个用户锁住数据库中的某个对象时,其他用户就不能再访问该对象。加锁对并发访问的影响体现在锁的粒度上。比如,放在一个表上的锁限制对整个表的并发访问;放在数据页上的锁限制了对整个数据页的访问;放在行上的锁只限制对该行的并发访问。可见行锁粒度最小,并发访问最好,页锁粒度最大,表锁介于2者之间。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1] 悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。 乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。
从数据库厂商的角度看,使用乐观的页锁是比较好的,尤其在影响很多行的批量操作中可以放比较少的锁,从而降低对资源的需求提高数据库的性能。再考虑聚集索引。在数据库中记录是按照聚集索引的物理顺序存放的。如果使用页锁,当两个用户同时访问更改位于同一数据页上的相邻两行时,其中一个用户必须等待另一个用户释放锁,这会明显地降低系统的性能。interbase和大多数关系数据库一样,采用的是乐观锁,而且读锁是共享的,写锁是排他的。可以在一个读锁上再放置读锁,但不能再放置写锁;你不能在写锁上再放置任何锁。锁是目前解决多用户并发访问的有效手段。
在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.
二、 装饰者模式和动态代理模式
装饰器模式:能动态的新增或组合对象的行为。
代理模式:为其他对象提供一种代理以控制对这个对象的访问.(换句话说,可以不执行某功能)
装饰模式是“新增行为”,而代理模式是“控制访问”。
装饰模式是在原有基础之上增加额外的功能,而代理模式有两种情况可以使用第一种是延迟代理对象,对于大文件或者目前还未加载完的对象,使用比较合适。第二种为权限判断,在调用功能前判断当前用户是否有此权限。
代理模式不是嵌套调用的。
装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案;
代理模式给一个对象提供一个代理对象,并有代理对象来控制对原有对象的引用;
装饰模式应该为所装饰的对象增强功能;
代理模式对代理的对象施加控制,并不提供对象本身的增强功能
代码区别:
装饰模式跟代理模式代码的最大的在于他们的构造方法,代理模式的构造方法不传参数,在构造方法内部完成参数传递,装饰模式将装饰的对象作为参数传进去。
理念区别:
代理模式中,代理类对被代理的对象有控制权,决定其执行或者不执行。而装饰模式中,装饰类对代理对象没有控制权,只能为其增加一层装饰,以加强被装饰对象的功能,仅此而已
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
三、getRealPath/getResource/getContentPath的区别
(1)在Servlet中取得路径:
(1.1)得到工程目录:request.getSession().getServletContext().getRealPath("") 参数可具体到包名。
结果:E:\Tomcat\webapps\TEST
(1.2)得到IE地址栏地址:request.getRequestURL()
结果:http://localhost:8080/TEST/test
(1.3)得到相对地址:request.getRequestURI()
结果:/TEST/test
从request获取各种路径总结
request.getRealPath(“url”); // 虚拟目录映射为实际目录
request.getRealPath("./"); // 网页所在的目录
request.getRealPath("…/"); // 网页所在目录的上一层目录
request.getContextPath(); // 应用的web目录的名称
(2)类的绝对路径:Class.class.getClass().getResource("/").getPath()
结果:/D:/TEST/WebRoot/WEB-INF/classes/pack/
(3)以工程名为TEST为例:
(3.1)得到包含工程名的当前页面全路径:request.getRequestURI()
结果:/TEST/test.jsp
(3.2)得到工程名:request.getContextPath()
结果:/TEST
(3.3)得到当前页面所在目录下全名称:request.getServletPath()
结果:如果页面在jsp目录下 /TEST/jsp/test.jsp
(3.4)得到页面所在服务器的全路径:application.getRealPath(“页面.jsp”)
结果:D:\resin\webapps\TEST\test.jsp
(3.5)得到页面所在服务器的绝对路径:absPath=new java.io.File(application.getRealPath(request.getRequestURI())).getParent();
结果:D:\resin\webapps\TEST
四、jsp中的动态包含和静态包含
一、静态包含指令<%@include file=“fileurl”%>
1、两个jsp页面的<%@page contentType=“text/html;charset=gbk”%>应该保持一致
2、不能通过fileurl向被包含的jsp页面传递参数,因为此静态包含是发生在jsp页面转换为servlet的转换期间,此时的参数是服务器端设置的死的参数,完全没有经过客户端,这种参数是没有意义的,如<%@include file=“fileurl?user=admin”%>,而且此时会报错。
3、包含的jsp页面与被包含的jsp页面共用一个request内置对象。
比如说在客户端访问包含页面时地址栏后面直接加上参数后传递,这种形式的传参是客户端送来的,两个页面都能够访问此参数。我们可以通过这两个页面合成的servlet中可以看到有传递的参数成为servlet的成员变量。
4、包含的jsp页面与被包含的jsp页面最好没有重复的html标签。否则会发生覆盖现象。
二、动态包含与静态包含<%@include file=“fileurl”%>的区别
1.动态包含用的元素是page,而且有两种形式。静态包含用的是file,只有一种形式。
2.生成的文件不同,静态的包含是将两个jsp文件二合一,生成一个以包含页面命名的servlet和class文件,动态包含的两个jsp文件各自生成自己的servlet和class文件。
-
传参方式一:<jsp:include page=“a.jsp?param=123”/>时被包含的jsp页面是可以访问该参数的。
-
传参方式二:
<jsp:include page=“a.jsp”>
<jsp:param name=“” value=“”>
<jsp:param name=“” value=“”>
</ jsp:include >
5.在客户端访问包含页面时地址栏后面直接加上参数后传递,这种形式的传参是客户端送来的,但是这两个页面的request对象不是同一个,因为3中已经说了包含的页面可以向被包含的页面传递参数,所以被包含的request对象含的参数个数应该大于等于包含页面的参数个数的。所以它们各有各的request对象。而且被包含的jsp页面可以访问传到包含页面的参数。
6.动态包含只有在执行到它的时候才加载,所以它才叫动态包含。
JSP虽然不能像M和C使用抽象、继承,但是它有自己的方式:包含。通过包含,可以讲JSP抽象出几个独立的部分,然后再组合起来,根据展示的不同,组合的方式也不同,从而达到各个部分之间的解耦和复用。包含又分为静态包含和动态包含。
静态包含
静态包含使用的标签是:
[html] view plain copy print?
<%@ include file="" %>
静态包含的意思是在编译前,将页面中使用到的JSP等合并为一个,然后再编译,因为编译前合并为了一个,所以各个部件之间不能有相同的变量名。
动态包含
动态包含使用的标签是:
[html] view plain copy print?
<jsp:include page="">
动态包含是在运行期间执行包含的文件,即各个部件之间分别编译,形成多个文件,因为分别编译,它们有各自的DOM结构,当然各个部件之间的变量名可以相同。
二者生成的HTML页是相同的,那么什么时候用动态包含?什么时候用静态包含?我的理解是:当部件之间联系较大,比如要使用相同的DOM结构、数据需要引用时,使用动态包含;当部件之间关系不大,比如是一些HTML静态内容时,动态包含和静态包含都可以。
五、Servlet、Fileter和Listener的生命周期
启动的顺序为listener->Filter->servlet.
执行的顺序不会因为三个标签在配置文件中的先后顺序而改变。
1.客户端请求该 Servlet;
2.加载 Servlet 类到内存;
3.实例化并调用init()方法初始化该 Servlet;
4.service()(根据请求方法不同调用doGet() 或者 doPost(),此外还有doHead()、doPut()、doTrace()、doDelete()、doOptions();
加载和实例化 Servlet。这项操作一般是动态执行的。然而,Server 通常会提供一个管理的选项,用于在 Server 启动时强制装载和初始化特定的 Servlet。
Server 创建一个 Servlet的实例
第一个客户端的请求到达 Server
Server 调用 Servlet 的 init() 方法(可配置为 Server 创建 Servlet 实例时调用,在 web.xml 中 标签下配置 标签,配置的值为整型,值越小 Servlet 的启动优先级越高)
一个客户端的请求到达 Server
Server 创建一个请求对象,处理客户端请求
Server 创建一个响应对象,响应客户端请求
Server 激活 Servlet 的 service() 方法,传递请求和响应对象作为参数
service() 方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息
service() 方法使用响应对象的方法,将响应传回Server,最终到达客户端。service()方法可能激活其它方法以处理请求,如 doGet() 或 doPost() 或程序员自己开发的新的方法。
对于更多的客户端请求,Server 创建新的请求和响应对象,仍然激活此 Servlet 的 service() 方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用 init() 方法。一般 Servlet 只初始化一次(只有一个对象),当 Server 不再需要 Servlet 时(一般当 Server 关闭时),Server 调用 Servlet 的 destroy() 方法
Filter的生命周期
web应用加载后会立即创建出当前web应用中的Filter对象, 创建出来后, 立即调用init方法进行初始化出操作 它们都提供了init(FilterConfig arg0)和destroy()方法来控制,当关闭web容器,关机,或者reload整个应用时,都会调用destroy()来关闭filter。也就是说,当web容器启动时,filter就被加载到内存,并在destroy()调用之前都常驻内存。。
Listener生命周期:一直从程序启动到程序停止运行。
ServletRequestListener:每次访问一个Request资源前,都会执行requestInitialized()方法,方法访问完毕,都会执行requestDestroyed()方法。
HttpSessionListener:每次调用request.getSession(),都会执行sessionCreated()方法,执行session.invalidate()方法,都会执行sessionDestroyed()方法。
ServletRequestAttributeListener:每次调用request.setAttribute()都会执行attributeAdded()方法,如果set的key在request里面存在,就会执行attributeReplacerd()方法,调用request.removeAttribute()方法,都会执行attributeRemoved()方法。
Filter生命周期:程序启动调用Filter的init()方法(永远只调用一次,具体看启动日志),程序停止调用Filter的destroy()方法(永远只调用一次,具体看关闭日志),doFilter()方法每次的访问请求如果符合拦截条件都会调用(程序第一次运行,会在servlet调用init()方法以后调用,不管第几次,都在调用doGet(),doPost()方法之前)。
Servlet生命周期:程序第一次访问,会调用servlet的init()方法初始化(只执行一次,具体看日志),每次程序执行都会根据请求调用doGet()或者doPost()方法,程序停止调用destory()方法(具体看结束日志)。
六、四大作用域
大概流程是这样的,我们访问04-01/index.jsp的时候,分别对pageContext, request, session,
application四个作用域中的变量进行累加。(当然先判断这个变量是不是存在,如果变量不存在,则要
把变量初始化成1。)计算完成后就从index.jsp执行forward跳转到test.jsp。在test.jsp里再进行一次
累加,然后显示出这四个整数来。
从显示的结果来看,我们可以直观的得出结论:
page里的变量没法从index.jsp传递到test.jsp。只要页面跳转了,它们就不见了。
request里的变量可以跨越forward前后的两页。但是只要刷新页面,它们就重新计算了。
session和application里的变量一直在累加,开始还看不出区别,只要关闭浏览器,再次重启浏览器访问
这页,session里的变量就重新计算了。
application里的变量一直在累加,除非你重启tomcat,否则它会一直变大。
而作用域规定的是变量的有效期限。
如果把变量放到pageContext里,就说明它的作用域是page,它的有效范围只在当前jsp页面里。
从把变量放到pageContext开始,到jsp页面结束,你都可以使用这个变量。
如果把变量放到request里,就说明它的作用域是request,它的有效范围是当前请求周期。
所谓请求周期,就是指从http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使
用forward的方式跳转了多个jsp页面,在这些页面里你都可以使用这个变量。
如果把变量放到session里,就说明它的作用域是session,它的有效范围是当前会话。
所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。这个过程可能包含多个
请求响应。也就是说,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被
称为一个会话(session),而放到会话中的变量,就可以在当前会话的所有请求里使用。
如果把变量放到application里,就说明它的作用域是application,它的有效范围是整个应用。
整个应用是指从应用启动,到应用结束。我们没有说“从服务器启动,到服务器关闭”,是因为一个服务
器可能部署多个应用,当然你关闭了服务器,就会把上面所有的应用都关闭了。
application作用域里的变量,它们的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。
与上述三个不同的是,application里的变量可以被所有用户共用。如果用户甲的操作修改了application
中的变量,用户乙访问时得到的是修改后的值。这在其他scope中都是不会发生的,page, request,
session都是完全隔离的,无论如何修改都不会影响其他人的数据。