过滤器、监听器、文件上传、会话技术、事务、ThreadLocal、更新丢失、开发模式

1.Filter

1.1.过滤器概念
1.1.1.过滤器的基本概念
Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
1.1.2.过滤器的功能
如图-1所示:
在这里插入图片描述
图-1
(1)过滤器可以拦截对资源的访问
(2)一个过滤器可以拦截多个资源,一个资源也可能被多个过滤器拦截
(3)所谓的拦截是根据访问的URL地址来确定访问的是哪个资源,确定是否拦截
(4)所谓的拦截其实就是拦截下来代表请求的request和响应的response
(5)拦截后:控制是否允许访问、访问之前和之外做一些额外操作

一些需要过滤器的情况:
1.认证Filter
2.日志和审核Filter
3.图片转换Filter
4.数据压缩Filter
5.密码Filter
6.令牌Filter
7.触发资源访问事件的Filter
8.XSLT Filter
9.媒体类型链Filter
过滤器的功能:
1.批量设置请求编码:可以解决中文乱码问题
request.serCharacterEncoding(“UTF-8”);
chain.doFilter(request,response);
2.用filter控制用户访问权限
3.日志和审核Filter
filter的应用:
1.转换字符编码
2.管理Hibernate的session
3.做权限审查
4.做cache
5.做拦截器的代理
6.过滤特殊字符、敏感词汇
1.2.过滤器开发入门
1.2.1.开发过滤器的两个步骤
想要开发一个过滤器,需要如下两个步骤。
(1)写一个类实现Filter接口
(2)在web.xml中配置过滤器
1.2.2.写一个类实现javax.servlet.Filter接口
如图-2所示:
在这里插入图片描述
图-2
init为初始化方法,在Filter对象被创建出来时,Servlet容器会调用该方法对filter进行初始化。
destory为销毁的方法,在过滤器对象被销毁之前,服务器会调用这个方法执行善后工作。
doFilter为过滤器中最核心的方法,当过滤器拦截到对资源的访问时,服务器会自动调用该方法执行过滤代码。 我们只需要在这个方法中设计过滤器的逻辑代码即可。
其中FilterConfig代表当前过滤器在web.xml中的配置。
FilterChain代表过滤器链。
一个资源可能被多个过滤器所拦截,拦截的顺序是按照这些过滤器的在web.xml中的配置顺序所决定
所有拦截当前资源的过滤器按照拦截顺序就组成了一个拦截器链,filterchain,代表的就是当前拦截器链.filterchain对象提供了一个dofilter的方法,一旦执行这个方法,表明当前过滤器放行,执行过滤器链的下一个节点,如果下一个节点没有过滤器了,就执行真正要访问的资源。
1.2.3.在web.xml中配置一下过滤器
— 配置过滤器
FirstFilter
—为过滤器起一个名字
com.itheima.filter.FirstFilter
—过滤器类的全路径名

— 可选的 可以配置多个 过滤器的初始化参数 可以在过滤器中通过FilterConfig对象来获取
name1
value1



— 配置过滤器的拦截路径 ,一个可以配置多个
FirstFilter
—为哪个过滤器进行配置
/*
— 拦截哪个路径 其中url-pattern的写法和学习Serlvet时的url-pattern的写法相同 这个url-pattern可以配置多个
Demo1Servlet
—拦截哪个名字的Servlet 可以配置多个
REQUEST
— 配置拦截哪种类型的对资源的访问,可选的值有REQUEST FORWARD INCLUDE ERROR,如果不配置默认只拦截REQUEST方式,如果具体配置了就拦截具体配置的方式的对资源的访问,此标签可以配置多个

1.3.Filter的生命周期
1.3.1.Filter的生命周期
当服务器启动,web应用加载后,立即创建出这个web应用中的所有过滤器对象,创建出来后立即调用过滤器的init方法执行初始化操作.从此这些过滤器对象驻留在内存中为后续的拦截进行服务.每当拦截到资源时,都会导致dofilter方法执行.最终直到服务器关闭或web应用移除出容器时,随着web应用的销毁,过滤器对象销毁,销毁之前调用destory方法执行善后工作。
1.4.FilterConfig接口
1.4.1.Filterconfig接口
用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。
1.4.2.Filterconfig接口提供的方法
FilterConfig提供的方法:
String getFilterName():得到filter的名称。
String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
public ServletContext getServletContext():返回Servlet上下文对象的引用。
1.5.EasyMall全站乱码解决过滤器
1.5.1.全站乱码过滤器
我们可以开发一个Filter,拦截所有的动态web资源,在动态web资源执行之前,解决好乱码,从而一劳永逸的解决全站乱码问题。
对于响应数据乱码,只需要设置Content-Type即可解决乱码。
对于请求参数乱码,如果是POST提交,可以一行代码解决,如果是GET提交需要手动的编解码解决乱码。
1.5.2.代码实现
如图-3所示:
在这里插入图片描述
图-3

全站乱码过滤器
EncodingFilter
com.itheima.filter.EncodingFilter


EncodingFilter
/

1.6.EasyMall实现30天内自动登录
1.6.1.30天自动登录
想要实现自动登录,需要通过Cookie保存用户名密码在客户端,然后通过过滤器拦截请求,对于未登录而带有自动登录cookie且自动登录cookie中保存的用户名密码都正确则在放行资源之前做自动登录操作。
如图-4所示:
在这里插入图片描述
图-4

自动登录过滤器
AutologinFilter
com.itheima.filter.AutologinFilter


AutologinFilter
/

1.7.MD5

1.7.1.MD5概述
用户名密码保存在客户端是一种十分危险的行为。所以需要进行加密后保存。
其中MD5就是一种比较常用的加密算法。
与其说MD5算法是一种加密算法,不如说是一种数据指纹(数据摘要)算法。
其特点如下:
任意大小的二进制数经过MD5计算后都能得到一个独一无二的128位二进制数。
不同的数据算出的MD5绝对不相同。
相同的数据算出的MD5一定相同。
只能有明文算出密文,密文是永远也无法算成明文的。
MD5大量应用于计算机中。如数据库中保存的密码通常都是经过MD5加密后的数据。如用户下载文件时可以进行MD5校验防止数据被篡改。
在记住用户名案例中,我们可以使用MD5进行加密后再保存在客户端,从而保证数据安全。
在数据库中保存的密码也不宜直接存储为明文。也要经过MD5加密后存储。
1.7.2.JAVA实现MD5加密
/**
* 使用md5的算法进行加密
*/
public static String md5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance(“md5”).digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(“没有md5这个算法!”);
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = “0” + md5code;
}
return md5code;
}

1.Listener

1.1.1.Servlet监听器概述
1.1.1.1.Servlet监听器概述
JavaEE中提供了八大监听器,用来监听Servlet中指定事件。
1.1.2.Servlet事件监听器步骤
实现web开发中的事件监听功能需要两个步骤:
(1)写一个类实现对应接口。
(2)在将写好的监听器类注册到web.xml中。
1.1.1.1.2.监听三个作用域对象创建和销毁的监听器
1.2.1.2.1.ServletContextListener 详解
ServletContextListener 监听器用于监听 ServletContext 对象的创建和销毁。
ServletContext域的生命周期:
服务器启动,web应用加载后,立即创建代表当前web应用的ServletContext对象,从此驻留在内存中,直到服务器关闭或web应用被移除出容器时,ServletContext对象被销毁。

1.2.2.HttpSessionListener详解
HttpSessionListener监听器用于监听HttpSession对象的创建和销毁。
HttpSession域的生命周期:
在第一次调用request.getSession()方法时创建。
超时过30分钟没人用(此时间可以在web.xml中修改)被销毁。
调用session.invalidate()方法被销毁。
服务器非正常关闭时销毁。
那么,当如果服务器是正常关闭,session会如何处置呢?
未超时的session会以文件的形式保存在服务器中,这个过程叫做session的钝化。在下一次服务器正常启动时,session还可以恢复回来,这个过程叫做session的活化。
*注意:想要随着session被钝化(活化)的对象,他的类必须实现Serializable接口。
作用范围:整个会话范围。

1.2.3.ServletRequestListener 详解
ServletRequestListener监听器用于监听ServletRequest对象的创建和销毁。
ServletRequest对象的生命周期:
请求开始,服务器创建代表这次请求的reuqest对象.
请求结束,服务器销毁代表请求的request对象。
1.1.1.1.2.1.3.监听域属性变化的监听器
1.3.1.监听域属性变化的监听器
ServletContextAttributeListener
HttpSessionAttributeListener
ServletRequestAttributeListener
这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。
1.3.2.监听域属性变化的监听器中的方法
(1)attributeAdded 方法
当向被监听器对象中增加一个属性时,web容器就调用事件监听器的 attributeAdded 方法进行相应,这个方法接受一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象。
(2)attributeRemoved 方法
当删除被监听对象中的一个属性时,web 容器调用事件监听器的这个方法进行相应。

(3)attributeReplaced 方法
当监听器的域对象中的某个属性被替换时,web容器调用事件监听器的这个方法进行相应。
1.2.2.1.2.2.2.3.1.4.监听JAVABEAN在Session域中状态变化的监听器
1.4.1.Javabean在Session中的状态
保存在 Session 域中的对象可以有多种状态:
(1)绑定到 Session 中。
(2)从Session 域中解除绑定。
(3)随Session 被钝化
(4)随Session被活化
1.4.2.监听JAVABEAN在Session域中状态变化的监听器
Servlet 规范中定义了两个特殊的监听器来使javabean感知自己在Session域中状态的变化:
(1)HttpSessionBindingListener接口
实现了HttpSessionBindingListener接口的 JavaBean 对象可以感知自己被绑定到 Session 中和从 Session 中删除的事件。
//当对象被绑定到 HttpSession 对象中时触发
void valueBound(HttpSessionBindingEvent event)
//当对象从 HttpSession 对象中解除绑定时触发
void valueUnbound(HttpSessionBindingEvent event)
(2)HttpSessionActivationListener接口
实现了HttpSessionActivationListener接口的 JavaBean 对象可以感知自己被活化和钝化的事件。
//当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被钝化时触发
sessionWillPassivate(HttpSessionBindingEvent event)
//当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被活化时触发
void sessionDidActive(HttpSessionBindingEvent event)方法。

2.文件上传

1.2.2.1.文件上传概述
1.2.2.1.2.1.1.文件上传是什么
在web开发中经常需要从客户端向服务器上传文件,如:上传照片、上传新闻图片、上传附件等等。这些都需要通过WEB开发中的文件上传技术实现。
1.1.1.1.2.1.2.文件上传步骤
实现web开发中的文件上传功能只需要两个步骤:
(1)提供一个带有文件上传项的表单。
(2)在servlet中读取处理上传的文件,保存到服务器中。
1.2.2.1.2.2.文件上传实现
2.2.2.2.1.在用户页面中添加上传输入项 (客户端页面操作)

注意事项:
(1) 必须为文件上传input 提供name属性,否则文件上传内容不会被表单提交
(2) 表单的提交方式必须为post (get提交数据在url地址上显示,有长度限制)
(3) 表单必须设置enctype=multipart/form-data
1.1.1.1.2.1.2.1.2.2.2.在服务器端编写文件上传程序
通过Request对象提供的一个getInputStream方法,可以读取到客户端提交过来的数据,但这种方式还需要对流中获取到的数据进行处理。
为了简化这个处理过程,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload ),让开发人员轻松实现web文件上传功能。
2.2.3.上传组件(Apache commons-fileupload)使用过程
首先需要下载并导入该组件相应的支撑jar包: Commons-fileupload和commons-io
然后编程实现,步骤如下:
(1)创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录。
public DiskFileItemFactory(int sizeThreshold, java.io.File repository)
构造工厂时,指定内存缓冲区大小和临时文件存放位置。
public void setSizeThreshold(int sizeThreshold)
设置内存缓冲区大小,默认10K。
public void setRepository(java.io.File repository)
设置临时文件存放位置,默认System.getProperty(“java.io.tmpdir”)。

内存缓冲区: 上传文件时,上传文件的内容优先保存在内存缓冲区中,当上传文件大小超过缓冲区大小,就会在服务器端产生临时文件
临时文件存放位置: 保存超过了内存缓冲区大小上传文件而产生临时文件

  • 产生临时文件可以通过 FileItem的delete方法删除
    (2)使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
    (3)调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。
    ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中。常用方法有:
    //判断上传表单是否为multipart/form-data类型。
    boolean isMultipartContent(HttpServletRequest request)
    //解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。
    List parseRequest(HttpServletRequest request)
    //设置单个上传文件的最大值。
    setFileSizeMax(long fileSizeMax)
    //设置上传文件总量的最大值。
    setSizeMax(long sizeMax)
    //解决上传文件名乱码问题。
    setHeaderEncoding(java.lang.String encoding)
    //实时监听文件上传状态。
    setProgressListener(ProgressListener pListener)
    (4)对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件。
    True 为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
    False 为上传文件项,则调用getInputStream方法得到数据输入流,从而读取上传数据。
    (5)如果是文件上传就通过流获取上传的数据保存到服务器中。
    boolean isFormField() 判断FileItem是一个文件上传对象还是普通表单对象
    普通字段项:
    String getFieldName() 获得普通表单对象的name属性
    String getString(String encoding) 获得普通表单对象的value属性,可以用encoding进行编码设置
    文件上传项:
    String getName() 获得上传文件的文件名(有些浏览器会携带客户端路径)
    InputStream getInputStream() 获得上传文件的输入流
    delete() 在关闭FileItem输入流后,删除临时文件
    3.3.1.3.2.3.3.上传文件的监听
    1.2.2.1.2.2.2.2.1.2.2.2.3.2.3.1.上传文件的监听
    ServletFileUpload类提供了如下方法,监听文件上传时的进度信息:
    public void setProgressListener(ProgressListener pListener)
    设置监听器,文件上传程序会自动执行,监听器中 update方法
    public void update(long pBytesRead, long pContentLength, int pItems)
    在方法中可以获得 文件总大小、已经上传大小和 上传第几个元素
    能否根据上面三个参数计算:剩余大小、传输速度、已用时间、剩余时间
    (1) 已用时间 = 当前时间 - 开始时间
    (2) 速度 = 已经上传大小/已用时间
    (3) 剩余大小 = 总大小- 已经上传大小
    (4) 剩余时间 = 剩余大小/速度

3.4.上传文件注意问题
2.4.2.4.1.文件保存的位置
上传的文件如果直接存放在web应用根目录下,通过浏览器是可以直接访问的,如果用户上传一个JSP,再通过浏览器访问就可以通过文件上传在服务器执行任意的java代码,十分危险。
所以一定要注意,文件上传服务器保存文件的位置必须是WEB-INF目录下或服务器中浏览器无法访问的位置。
2.4.2.防止上传的文件重名覆盖
上传的文件如果重名,后上传的文件就会覆盖先上传文件 。
为了防止这样的问题产生,应该对文件名进行处理,可以在文件名前拼接UUID,防止文件名重复。
filename = UUID.randomUUID().toString() + “_” + filename;

2.4.3.防止同一个目录下上传文件过多
一个目录下如果文件过多,必然会导致访问效率下降。所以我们需要将上传的文件进行分目录存储。
分目录存储的算法可以有很多:
(1) 按照上传时间进行目录分离 (周、月 )
(2) 按照上传用户进行目录分离 ----- 为每个用户建立单独目录
(3) 按照固定数量进行目录分离 ------ 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
(4) 按照唯一文件名的hashcode 进行目录分离
public static String generateRandomDir(String uuidFileName) {
// 获得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
// 获得一级目录
int d1 = hashcode & 0xf;
// 获得二级目录
int d2 = (hashcode >>> 4) & 0xf;
return “/” + d2 + “/” + d1;// 共有256目录
}
2.4.4.文件上传代码实现

	response.setContentType("text/html;charset=utf-8");
	try {
		//1.获取文件上传工厂类
		DiskFileItemFactory factory = new DiskFileItemFactory();
		//--设置内存缓冲区的大小,默认是10KB
		factory.setSizeThreshold(1024*100);
		//--设置临时文件夹的位置
		factory.setRepository(new File(this.getServletContext().getRealPath("WEB-INF/temp")));
		//2.获取文件上传的核心类
		ServletFileUpload fileUpload = new ServletFileUpload(factory);

		//--检查当前表单是否是multipart/form-data类型
		if(!fileUpload.isMultipartContent(request)){
			throw new RuntimeException("请使用正确的表单上传数据~!");
		}
		//--设置单个文件大小限制
		//fileUpload.setFileSizeMax(1024*1024*10);
		//--设置总大小限制
		//fileUpload.setSizeMax(1024*1024*20);
		//--设置编码解决上传文件名的乱码问题
		fileUpload.setHeaderEncoding("utf-8");
		//--设置文件上传监听
		//3.解析request
		List<FileItem> list = fileUpload.parseRequest(request);
		//4.遍历集合拿到每一个FileItem处理
		for(FileItem item : list){
			if(item.isFormField()){
				//--当前item代表的是一个普通字段项
				String name = item.getFieldName();
				String value = item.getString("utf-8");
				System.out.println(name+":"+value);
			}else{
				//--当前item代表的是一个文件上传项
				String fname = item.getName();
				fname = UUID.randomUUID().toString()+"_"+fname;
				
				String hash = Integer.toHexString(fname.hashCode());
				String path = "WEB-INF/upload";
				for(char c : hash.toCharArray()){
					path = path +"/"+ c;
				}
				new File(this.getServletContext().getRealPath(path)).mkdirs();
				
				InputStream in = item.getInputStream();
				OutputStream out = new FileOutputStream(this.getServletContext().getRealPath(path+"/"+fname));
				
				byte [] bs = new byte [1024];
				int i = 0;
				while((i=in.read(bs))!=-1){
					out.write(bs,0,i);
				}
				
				in.close();
				out.close();
				
				//--删除临时文件
				item.delete();
			}
		}
	} catch (SizeLimitExceededException e) {
		response.getWriter().write("单个文件不能超过10M,总大小不能超过20M!");
		return;
	}catch (FileUploadException e) {
		e.printStackTrace();
		throw new RuntimeException(e);
}

1.会话技术

1.1.会话技术
1.1.1.会话概述
会话可简单理解为:用户开一个浏览器,访问多个资源,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。
1.1.2.会话中的问题
每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。
如用户登录过后,应该保存一个用户状态,在会话结束前表明用户一直是登录状态。并且不同的用户之间的登录状态应该互不影响。
我们之前已经学过request域和SerlvetContext域,我们分别分析一下。
Request域太小了,在多次请求中,每次请求响应都是新的Request对象,之前存入的request域中的域属性,在请求结束后随着request域的销毁而消失了。
ServletContext域太大了,所有用户都在操作这个域,必然会发生混乱。
此时我们需要会话相关的技术,保存会话产生的数据。
图-8

2.Cookie技术

2.1.Cookie技术
2.1.1.Cookie概述
Cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。如图-9所示:
在这里插入图片描述
图-9
cookie是会话相关的技术,可以保存会话产生的数据,客户端技术,将数据保存在客户端,基于set-Cookie响应头和Cookie请求头工作的.
2.1.2.Cookie常用方法
Cookie cookie = new Cookie(String name,String value);
–javax.servlet.http.Cookie类用于创建一个Cookie

setValue与getValue方法
–设置和获取cookie的值

getName方法

–cookie需要在创建时就指定好名字,这个名字一旦指定就不能修改了,如果需要修改只能重新创建一个cookie

setMaxAge与getMaxAge方法
–一个cookie被发送到浏览器时,如果没有设置过MaxAge,这个cookie将会被保存在浏览器的内存中,会一直存在到浏览器关闭内存销毁为止,这样的cookie叫做会话级别的Cookie
–我们也可以使用setMaxAge方法设置cookie的存活时间,这样一来,浏览器收到这个Cookie信息时会将cookie信息以文件的形式保存在浏览器的临时文件夹中,保存到指定的时间到来位置,在这个期间即使多次开关浏览器,cookie信息一直都在.

setPath与getPath方法
–设置浏览器在访问哪个地址及其子地址时,带着cookie信息过来.如果没设置过,那么默认的path是当前发送cookie的Servlet所在的路径

setDomain与getDomain方法
–设置浏览器在访问哪个域名时,带着当前的cookie,默认值当前发送cookie的域名,注意,现代的浏览器只要发现cookie设置过maxage,则认为这个cookie是第三方cookie,会拒绝接受

2.1.3.发送cookie
response.addCookie(cookie);
–response接口也中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段。
2.1.4.获取cookie
request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie。
Cookie [] cs = request.getCookies();
2.1.5.删除cookie
浏览器会通过名字+path+domaint来区分cookie,如果浏览器发现两个cookie名字 path domain 都相同,则会认为是相同的cookie,就会发送cookie的覆盖。
所以如果想要删除一个cookie,可以发送一个和要删除的cookie名字 path (domain) 都相同的cookie,然后将这个cookie的maxAge设置为0,这样一来,这个cookie会把要删除的cookie覆盖掉,覆盖后立即超时,直接被浏览器删除,看起来就像是删除了要删除的cookie
2.1.6.Cookie细节
一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。
注意,删除cookie时,path必须一致,否则不会删除(浏览器通过cookie的name+path来标识一个cookie)
2.2.EasyMall实现记住用户名功能
2.2.1.记住用户名功能分析
登录页面如图-10所示:
在这里插入图片描述
图-10
如果用户在登录时勾选了记住用户名,则应该将用户名称用户名保存起来,在用户下次登录时,读取保存的用户名,显示出来。
如果用Session保存,我们知道,Session一旦30分钟不使用就会失效,这个时间显然太短了。
而用Cookie保存,可以设置MaxAge指定保存的时常,直到指定时间结束或用户手动清楚cookie之前,此信息一直存在。
所以我们选择Cookie来实现保存用户名的功能。
2.2.2.代码实现
在用户登录的Servlet中,当用户登录成功后,判断用户是否勾选过记住用户名,如果用户勾选过,则发送cookie保存用户名,注意MaxAge设置为30天。
//查询数据库是否存在指定用户名密码的用户
try {
SAXReader reader = new SAXReader();
Document dom = reader.read(this.getServletContext().getRealPath(“WEB-INF/classes/users.xml”));
Element root = dom.getRootElement();

Element ele = (Element) root.selectSingleNode(

“//user[@username=’”+username+"’ and @password=’"+password+"’]"
);
if(ele == null){
//没有找到,提示用户名密码不正确
response.getWriter().write(“用户名密码不正确!”);
return;
}else{
//用户名密码正确,将用户名保存到session中表明用户登录过
request.getSession().setAttribute(“user”, username);
//检查用户是否勾选过记住用户名
if(“true”.equals(request.getParameter(“remname”))){
//发送cookie保存用户名
Cookie remnamec = new Cookie(“remname”,username);
remnamec.setPath("/");
remnamec.setMaxAge(36002430);
response.addCookie(remnamec);
}
//回到主页
response.sendRedirect("/index.jsp");
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
经过测试,发现当用户勾选过记住用户名时,会发送cookie给浏览器保存用户名。如图-11所示:
在这里插入图片描述
图-11
当浏览器再次访问登录页面时,请求中带了cookie,其中保存着用户名。如图-12所示:
在这里插入图片描述
图-12
现在我们只需要在登录页面,读取到cookie中保存的用户名显示到用户名输入项即可。
我们当前输出登录页面用的是html页面,html是静态web资源无法在其中写代码读取cookie数据,所以我们改造为用jsp实现。
将login.html改为login.jsp,在其中嵌入java代码,读取请求中的cookie信息,填入用户名输入框。如图-13所示:
在这里插入图片描述
图-13
经测试,可以正确的显示用户名,如图-14所示:
在这里插入图片描述
图-14
上面的测试中我们用的用户名是英文的,再测试一下中文。如图-15所示:
在这里插入图片描述
图-15
提交后发现报错,如图-16所示:
在这里插入图片描述
图-16
为什么会报错呢?我们知道cookie是基于HTTP协议中的set-Cookie请求头和Cookie响应头工作的。而HTTP协议本身只支持ISO8859-1编码,ISO8859-1中没有中文,所以报错了。
那么该如何解决呢?
Web开发中有一种常用的编码方式叫URL编码就是用来解决这种问题的。
3.URL编码
3.1.URL编码
3.1.1.URL编码概述
其实这种编码方式我们见过,当我们用浏览器提交中文数据时,中文数据也是经过URL编码后提交的。对于非iso8859-1的字符,浏览器和服务器默认使用URL编码进行处理。如图所示:
在这里插入图片描述
3.1.2.URL编码原理

3.1.3.URL编解码
Java中提供了进行URL编码和解码的类
URLEncoder:static String encode(String s,String enc);
//将指定的字符串按照指定的编码转换为URL编码的形式
URLDecoder:static String decode(String s,String enc);
//将URL编码后的字符串按照指定编码解码为源字符串
3.2.EasyMall记住用户名中文处理
3.2.1.EasyMall记住用户名中文处理
我们可以通过URL编码解决集中用户名中的中文问题。
改造代码,在发送cookie时,进行URL编码
//检查用户是否勾选过记住用户名
if(“true”.equals(request.getParameter(“remname”))){
//发送cookie保存用户名
Cookie remnamec =
new Cookie(“remname”,URLEncoder.encode(username, “utf-8”));
remnamec.setPath("/");
remnamec.setMaxAge(36002430);
response.addCookie(remnamec);
}
经测试cookie中保存的是进过URL编码的用户名。如图-17所示:
在这里插入图片描述
图-17
在login.jsp中,用户名要经过URL解码,如图-18所示:
在这里插入图片描述
图-18
经测试,可以正常的处理中文用户名。如图-19所示:
在这里插入图片描述
图-19
4.session的原理
4.1.Session原理
4.1.1.Session原理
服务器是如何区别出当前请求是要对应到哪个Sessoin的呢?其实Session是基于一个特殊的名为JSESSIONID的cookie工作的。如图-20所示:
在这里插入图片描述

图-20
4.2.利用Session原理实现即使多浏览器共用Session
4.2.1.代码实现
利用sesison的原理,我们可以自己写出一个JSESSIONID cookie,设置maxAge,从而实现即使关闭浏览器仍能找到之前的session
在这里插入图片描述
图-21
4.3.禁用cookie时使用session
4.3.1.禁用cookie时使用session
当客户端禁用了cookie时,造成session无法访问,此时我们可以使用URL重写来解决这个问题
URL重写要求将站点中的所有超链接都进行改造,在超链接后用一个特殊的参数JSESSIONID保存当前浏览器对应session的编号,这样一来,当用户点击超链接访问服务器时,服务器可以从URL后的参数中分析出JSESSIONID,从而找到对应的sesison使用.
URL重写之前,要先创建出session,才能进行重写操作
response.encodeURL(String url);
//–如果是普通的地址用这个方法
response.encodeRedirectURL(String url);
//–如果地址是用来进行重定向的,用这个方法
以上两个方法可以实现URL重写,这两个方法非常的智能,只要发现浏览器发送了任何Cookie过来,就认为当前客户端没有禁用Cookie,就不会在进行URL重写

1.事务

1.1.事务的概念
1.1.1.事务的概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。
例如:A——B转帐,对应于如下两条sql语句
update account set money=money-100 where name=‘a’;
update account set money=money+100 where name=‘b’;
1.2.管理事务
1.2.1.数据库默认的事务
数据库默认支持事务的,但是数据库默认的事务是一条sql语句独占一个事务,这种模式,意义不大.
1.2.2.手动控制事务
如果希望自己控制事务也是可以的:
start transaction;
– 开启事务,在这条语句之后的所有的sql将处在同一事务中,要么同时完成要么同时不完成

–事务中的sql在执行时,并没有真正修改数据库中的数据
commit;
– 提交事务,将整个事务对数据库的影响一起发生
rollback;
– 回滚事务,将这个事务对数据库的影响取消掉
1.2.3.JDBC中控制事务
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
conn.setAutoCommit(false);
–关闭自动连接后,conn将不会帮我们提交事务,在这个连接上执行的所有sql语句将处在同一事务中,需要我们是手动的进行提交或回滚
conn.commit();
–提交事务
conn.rollback();
–回滚事务
也可以设置回滚点回滚部分事务。
SavePoint sp = conn.setSavePoint();
conn.rollback(sp);
–注意,回到回滚点后,回滚点之前的代码虽然没被回滚但是也没提交呢,如果想起作用还要做commit操作.
ThreadLocal - 线程本地变量
TransactionManager中的Connection如果管理?
如果所有的线程都使用同一个Connection 则事务混乱 - 多线程安全问题

	方案1:在类的内部通过 静态Connection 加锁 的方式来管理 - 可以解决问题 但是 所有事务排队 效率非常低

	方案2:通过ThreadLocal编码 实现 所有的线程都各自携带各自的Connection对象 从而让管理各自的事务 - 不会有阻塞 效率高
1.ThreadLocal概述
	Thread对象内置了一个Map来存取消息,但是这个map外界无法直接操作
	需要通过ThreadLocal来实现对Thread中的Map进行数据的存取

	本质上是一种利用线程的执行过程由程序的上游 向程序下游传递信息的机制
	又由于 而每个线程都可以保存自己对应的对象 后续获取来使用 每个线程给自有各自的对象 自然就不会有线程安全问题了

2.ThreadLocal方法
	ThreadLocal tl = new ThreadLocal();
	tl.set(obj); --> 获取当前线程 得到当前线程内部的map 向map中存储 tl->obj 的键值对
	tl.get(); --> 获取当前线程 得到当前线程内部的map 从map中查找tl对应的值 返回
	tl.remove(); --> 获取当前线程 得到当前线程内部的map 从map中移除tl键值对

1.3.事务的四大特性

1.3.1.事务的四大特性
事务的四大特性是事务本身具有的特点。简称ACID。
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务前后数据的完整性必须保持一致。
隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
1.4.隔离性
1.4.1.数据库隔离性分析
数据库的其他三大特性数据库可以帮我们保证,而隔离性我们需要再讨论。
如果我们是数据库的设计者,该如何考虑设计数据库保证数据库的隔离性呢?
我们知道数据库的隔离性问题本质上就是多线程并发安全性问题。
可以用锁来解决多线成并发安全问题,但是如果用了锁,必然会造成程序的性能大大的下降.对于数据库这种高并发要求的程序来说这是不可接受的.
我们可以具体分析下隔离性产生的细节:
如果两个线程并发修改,必然产生多线程并发安全问题,必须隔离开
如果两个线程并发查询,必然没有问题,不需要隔离
如果一个线程修改,一个线程查询,在不同的应用场景下有可能有问题,有可能没问题。
1.5.隔离性可能造成的问题
1.5.1.脏读:
一个事务读取到另一个事务未提交的数据
----------------------------
a 1000
b 1000
----------------------------
a:
start transaction;
update account set money=money-100 where name=a;
update account set money=money+100 where name=b;
-----------------------------
b:
start transaction;
select * from account;
a 900
b 1100
commit;
-----------------------------
a:
rollback;
-----------------------------
b:
start transaction;
select * from account;
a 1000
b 1000
commit;
-----------------------------

1.5.2.不可重复读:
一个事务多次读取数据库中的同一条记录,多次查询的结果不同(一个事务读取到另一个事务已经提交的数据)
------------------------------
a 1000 1000 1000
------------------------------
b:
start transaction;
select 活期 from account where name=‘a’; — 活期存款:1000元
select 定期 from account where name = ‘a’; — 定期存款:1000元
select 固定 from account where name = ‘a’; — 固定资产:1000元
---------------------------
a:
start transaction;
update account set 活期=活期-1000 where name= ‘a’;
commit;
---------------------------
select 活期+定期+固定 from account where name=‘a’; —总资产:2000元
1.5.3.虚读(幻读)
有可能出现,有可能不出现:一个事务多次查询整表数据,多次查询时,由于有其他事务增删数据, 造成的查询结果不同(一个事务读取到另一个事务已经提交的数据)
------------------------------
a 1000
b 2000
------------------------------

d:
start transaction;
select sum(money) from account; --- 总存款3000元
select count(*) from account; --- 总账户数2个
	-----------------
	c:
		start transaction;
		insert into account values ('c',3000);
		commit;
	-----------------
select avg(mone) from account; --- 平均每个账户:2000元

1.6.数据库的隔离级别

1.6.1.四大隔离级别
那么数据库设计者在设计数据库时到底该防止哪些问题呢?防止的问题越多性能越低,防止的问题越少,则安全性越差。
到底该防止哪些问题应该由数据库使用者根据具体的业务场景来决定,所以数据库的设计者并没有把放置哪类问题写死,而是提供了如下选项:
数据库的四大隔离级别:
Read uncommitted;
— 不做任何隔离,可能造成脏读 不可重复度 虚读(幻读)问题
Read Committed;
– 可以防止脏读,但是不能防止不可重复度 虚读(幻读)问题
Repeatable Read;
– 可以防止脏读 不可重复度,但是不能防止 虚读(幻读)问题
Serializable;
– 可以防止所有隔离性的问题,但是数据库就被设计为了串行化的数据库,性能很低
从安全性上考虑:
Serializable > Repeatable Read > Read Committed > Read uncommitted
从性能上考虑:
Read uncommitted > Read committed > Repeatable Read > Serializable

我们作为数据库的使用者,综合考虑安全性和性能,从四大隔离级别中选择一个在可以防止想要防止的问题的隔离级别中性能最高的一个.
其中Serializable性能太低用的不多,Read uncommitted安全性太低用的也不多,我们通常从Repeatable Read和Read committed中选择一个.
如果需要防止不可重复读选择Repeatable Read,如果不需要防止选择Read committed

mysql数据库默认的隔离级别就是Repeatable Read
Oracle数据库默认的隔离级别是Read committed

1.7.操作数据库的隔离级别

1.7.1.查询数据库的隔离级别
select @@tx_isolation;
1.7.2.修改数据库的隔离级别
set [session/global] transaction isolation level xxxxxx;
不写默认就是session,修改的是当前客户端和服务器交互时是使用的隔离级别,并不会影响其他客户端的隔离级别
如果写成global,修改的是数据库默认的隔离级别(即新开客户端时,默认的隔离级别),并不会修改当前客户端和已经开启的客户端的隔离级别
1.8.数据库中的锁:
1.8.1.共享锁
共享锁和共享锁可以共存,共享锁和排他锁不能共存.在非Serializable隔离级别下做查询不加任何锁,在Serializable隔离级别下做查询加共享锁.

1.8.2.排他锁
排他锁和共享锁不能共存,排他锁和排他锁也不能共存,在任何隔离级别下做增删改都加排他锁.
1.8.3.可能的死锁
mysql可以自动检测到死锁,错误退出一方执行另一方
1.9.更新丢失问题
1.9.1.更新丢失问题的产生
两个并发的事务基于同一个查询结果进行修改,后提交的事务忽略了先提交的事务对数据库的影响,造成了先提交的事务对数据库的影响丢失,这个过程就叫做更新丢失
案例:在线支付案例
案例:修改小红案例
1.9.2.更新丢失解决方案:
将数据库设置为Serializable隔离级别,但是我们一般不会将数据库设置为Serializable
那么在非Serializable下又如何解决更新丢失?可以使用乐观锁、悲观锁。
乐观锁和悲观锁并不是数据库中真实存在的锁,而是两种解决方案的名字。
(1)悲观锁:
在查询时,手动的加排他锁,从而在查询时就排除可能的更新丢失。
select * from users for update;
(2)乐观锁:
在表中设计版本字段,在进行修改时修改时,要求根据具体版本进行修改,并讲版本字段+1,如果更新失败,说明更新丢失,需要重新进行更新。
两种解决方案各有优缺点,如果查询多修改少,用乐观锁.如果修改多余查询,用悲观锁。

23种软件设计模式:

装饰者模式
代理模式

模式:并不是某一个大牛想出来的,是众多软件开发人员在大量的开发中总结的,针对通用问题的通用性的解决方案
零、javaee设计模式的发展
Servlet
JSP
JSP + JavaBean
Servlet + JavaBean + Jsp – 符合mvc
javaee的经典三层架构 – 符合mvc
一、MVC设计模式
软件可以认为有 Model View Controller 来组成 MVC设计模式 要求这三部分 应该尽量独立 互不干扰 使程序结构清晰 便于开发和维护
二、JAVAEE经典三层架构
将整个JAVAEE的开发过程分为 Web Service Dao 三层
案例:改造EasyMall 为 三层架构
分层的优势:
~1.分层目的在于 代码具有更加优良的结构 便于开发和调试
~2.便于在层与层之间实现代码的复用 减少代码冗余
~3.在更改模块时可以不影响其他模块的使用 实现模块的复用
如 需要将底层的数据库 从mysql 切换到oracle 此时 只需要去修改Dao层 web层和service层不需要修改。
再例如 ,需要将web层 切换为 用Android 或 IOS 移动端实现 ,此时 只需要 替换web 层 Service 和 Dao层不需要修改。
为了实现第三个优点,三层架构要求,层与层之间尽量的独立,不要胡乱传递层特有的对象,例如不要将web层特有的对象传递给其他层,dao层 特有的对象返回给其他层,因为,一旦这样做,这些某一层特有的对象就入侵到了其他层,一旦需要替换这一层时,不可避免的要跟着修改其他层中使用了这一层特有对象的地方的代码。这种由于乱传递对象而造成 层与层之间关系变得过于紧密的情况,称之为层与层之间发生了耦合。
这种层与层之间过于紧密的关系是有害的,应该尽量的避免或消除,解决这些耦合的过程就称之为 解耦。最终希望实现 高内聚 低耦合 的状态。
解耦的实现:
不要胡乱传递层特有的对象人为的造成耦合。
但是再小心,层与层之间早晚要发生关系的,这些耦合是无法避免的,此时 需要将耦合管理起来 - 管理耦合有很多种方案 - 我们介绍 - 接口+配置文件+工厂 实现解耦。
接口:
使层可以面向接口编程 而不需要关注下面层具体的实现
在切换底层时 不需要修改上层代码 因为接口没有变动
为层的实现提供了统一的标准 方便 实现不同实现类
工厂+配置文件:
基于配置来实现产生接口的实现类
由工厂来根据配置来生产接口的具体实现类对象,在底层实现类变化时,可以不需要修改代码 只需要改配置即可
单例 泛型 反射

=====================================================
单例模式:
解决的问题:保证一个类在一个程序中只能有唯一的一个实例
2种情况:
1. 需要唯一的一个对象进行统一的管理,如果对象多了,就会出问题
2. 相对于频繁创建对象,使用唯一的对象可以提高效率
如何实现单例模式?
1. 私有的构造器
2. 私有的静态的自身实例,作为唯一的实例
3. 公有的静态的返回当前类的唯一实例的方法

==============================================================================================
EasyMall重构:
jsp中的java代码用标签技术替代
配置虚拟主机,实现www.easymall.com访问
在[tomcat]/conf/server.xml配置虚拟主机
修改Hosts文件增加 127.0.0.1 www.easymall.com
在[tomcat]/conf/www.easymall.com/ROOT.xml 中编写直接让tomcat中的www.easymall.com虚拟主机管理工程目录下的WebRoot,好处是不用发布,直接修改工程中的文件立即可以起作用。
**此种方式下没有了发布过程,myeclipse就不会自动帮我们加入jstl开发包了,需要自己导入。
用javabean保存信息
整理代码结构,实现软件分层
注册用户:
RegistServlet
解决请求响应乱码
验证验证码
获取用户提交的数据
校验数据
将数据封装到bean中,调用Service的方法注册用户
注册成功后回到主页
UserService
注册用户的方法:
检查用户名是否存在
注册用户到数据库
UserDao
根据用户名查找用户
加入用户数据到数据库中
登录用户:
LoginServlet
处理乱码
获取用户名密码
记住用户名的处理
调用Service根据用户名密码查找用户
找不到就提示用户名密码不正确
找到就登录用户回到主页
UserService
登录用户的方法
调用到根据用户名密码查找用户
UserDao
根据用户名密码查找用户的方法
AJAX校验用户名:
解决乱码
获取用户名
检查用户名是否已经存在

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值