有一种落差是,你的才华配不上梦想
你的能力配不上野心,也辜负了所受的苦难。

解决表单重复提交问题

liaoshengzhe阅读(7125)

在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。

一、表单重复提交的常见应用场景

有如下的form.jsp页面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML> <html>   <head>     <title>Form表单</title>   </head>  <body>   <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">   用户名:<input type="text" name="username">   <input type="submit" value="提交" id="submit">   </form>  </body> </html>

form表单提交到DoFormServlet进行处理

package xdp.gacl.session; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DoFormServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)  throws ServletException, IOException {  //客户端是以UTF-8编码传输数据到服务器端的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码  request.setCharacterEncoding("UTF-8");  String userName = request.getParameter("username");  try {  //让当前的线程睡眠3秒钟,模拟网络延迟而导致表单重复提交的现象  Thread.sleep(3*1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("向数据库中插入数据:"+userName);  } public void doPost(HttpServletRequest request, HttpServletResponse response)  throws ServletException, IOException {  doGet(request, response);  } }

如果没有进行form表单重复提交处理,那么在网络延迟的情况下下面的操作将会导致form表单重复提交多次

1、在网络延迟的情况下,用户多次点击提交按钮

2、表单提交后用户点击刷新按钮导致表单重复提交

3、用户提交表单后,点击浏览器的后退按钮回到表单页面后再次提交

二、利用JavaScript防止表单重复提交

既然存在上述所说的表单重复提交问题,那么我们就要想办法解决,比较常用的方法是采用JavaScript来防止表单重复提交,具体做法如下:

修改form.jsp页面,添加如下的JavaScript代码来防止表单重复提交

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
 <head>
 <title>Form表单</title>
 <script type="text/javascript">
 var isCommitted = false;//表单是否已经提交标识,默认为false
 function dosubmit(){
 if(isCommitted==false){
 isCommitted = true;//提交表单后,将表单是否已经提交标识设置为true
 return true;//返回true让表单正常提交
 }else{
 return false;//返回false那么表单将不提交
 }
 }
 </script>
 </head>
 
 <body>
 <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" onsubmit="return dosubmit()" method="post">
 用户名:<input type="text" name="username">
 <input type="submit" value="提交" id="submit">
 </form>
 </body>
</html>

可以看到,针对”在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交“这个应用场景,使用JavaScript是可以解决这个问题的,解决的做法就是用JavaScript控制Form表单只能提交一次“。

除了用这种方式之外,经常见的另一种方式就是表单提交之后,将提交按钮设置为不可用,让用户没有机会点击第二次提交按钮,代码如下:

function dosubmit(){
 //获取表单提交按钮
 var btnSubmit = document.getElementById("submit");
 //将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
 btnSubmit.disabled= "disabled";
 //返回true让表单可以正常提交
 return true;
}

另外还有一种做法就是提交表单后,将提交按钮隐藏起来,这种做法和将提交按钮设置为不可用是差不多的,个人觉得将提交按钮隐藏影响到页面布局的美观,并且可能会让用户误以为是bug(怎么我一点击按钮,按钮就不见了呢?用户可能会有这样的疑问),我个人在开发中用得比较多的是表单提交后,将提交按钮设置为不可用,反正使用JavaScript防止表单重复提交的做法都是差不多的,目的都是让表单只能提交一次,这样就可以做到表单不重复提交了。

使用JavaScript防止表单重复提交的做法只对上述提交到导致表单重复提交的三种场景中的【场景一】有效,而对于【场景二】和【场景三】是没有用,依然无法解决表单重复提交问题。

三、利用session防止表单重复提交

对于【场景二】和【场景三】导致表单重复提交的问题,既然客户端无法解决,那么就在服务器端解决,在服务器端解决就需要用到session了。

具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
  2. 当前用户的Session中不存在Token(令牌)。
  3. 用户提交的表单数据中没有Token(令牌)。

看具体的范例:

1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面

 

package xdp.gacl.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FormServlet extends HttpServlet {
 private static final long serialVersionUID = -884689940866074733L;

public void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {

String token = TokenProccessor.getInstance().makeToken();//创建令牌
 System.out.println("在FormServlet中生成的token:"+token);
 request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
 request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面
 }

public void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 doGet(request, response);
 }

}

2.在form.jsp中使用隐藏域来储存Token

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>form表单</title>
</head>

<body>
 <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
 <%--使用隐藏域存储生成的token--%>
 <%--
 <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
 --%>
 <%--使用EL表达式取出存储在session中的token--%>
 <input type="hidden" name="token" value="${token}"/> 
 用户名:<input type="text" name="username"> 
 <input type="submit" value="提交">
 </form>
</body>
</html>

3.DoFormServlet处理表单提交

package xdp.gacl.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DoFormServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {

boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
 if(b==true){
 System.out.println("请不要重复提交");
 return;
 }
 request.getSession().removeAttribute("token");//移除session中的token
 System.out.println("处理用户提交请求!!");
 }
 
 /**
 * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
 * @param request
 * @return 
 * true 用户重复提交了表单 
 * false 用户没有重复提交表单
 */
 private boolean isRepeatSubmit(HttpServletRequest request) {
 String client_token = request.getParameter("token");
 //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
 if(client_token==null){
 return true;
 }
 //取出存储在Session中的token
 String server_token = (String) request.getSession().getAttribute("token");
 //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
 if(server_token==null){
 return true;
 }
 //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
 if(!client_token.equals(server_token)){
 return true;
 }
 
 return false;
 }

public void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 doGet(request, response);
 }

}

生成Token的工具类

package xdp.gacl.session;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import sun.misc.BASE64Encoder;

public class TokenProccessor {

/*
 *单例设计模式(保证类的对象在内存中只有一个)
 *1、把类的构造函数私有
 *2、自己创建一个类的对象
 *3、对外提供一个公共的方法,返回类的对象
 */
 private TokenProccessor(){}
 
 private static final TokenProccessor instance = new TokenProccessor();
 
 /**
 * 返回类的对象
 * @return
 */
 public static TokenProccessor getInstance(){
 return instance;
 }
 
 /**
 * 生成Token
 * Token:Nv6RRuGEVvmGjB+jimI/gw==
 * @return
 */
 public String makeToken(){ //checkException
 // 7346734837483 834u938493493849384 43434384
 String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
 //数据指纹 128位长 16个字节 md5
 try {
 MessageDigest md = MessageDigest.getInstance("md5");
 byte md5[] = md.digest(token.getBytes());
 //base64编码--任意二进制编码明文字符 adfsdfsdfsf
 BASE64Encoder encoder = new BASE64Encoder();
 return encoder.encode(md5);
 } catch (NoSuchAlgorithmException e) {
 throw new RuntimeException(e);
 }
 }
}

 

Redis缓存服务器在java中的用法

liaoshengzhe阅读(7266)

一、Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
二、在java中通常按照以下方式使用
jar包链接jedis-2.7.0.jar【下载后自行改名】
封装好的工具类链接JedisPoolUtils.java【点击可浏览】

①导入必备的jar包【jedis-2.7.0.jar】这个jedis是Redis的java版本的客户端实现。
Jedis使用commons-pool完成池化实现 封装一个JedisPool的工具类
先做个配置文件(.properties文件)

redis.maxIdle=30
redis.minIdle=10
redis.maxTotal=100
redis.url=localhost
redis.port=6379

分别是最大闲置连接数、最小闲置连接数、最大连接数、服务器地址、端口号
封装JedisPool工具类

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolUtils {
	private static JedisPool pool = null;
	static{
		//加载配置文件
		InputStream in = JedisPoolUtils.class.getClassLoader().getResourceAsStream("redis.properties");
		Properties pro = new Properties();
		try {
			pro.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//获得池子对象(redis数据库)
		JedisPoolConfig poolConfig = new JedisPoolConfig();
	
		poolConfig.setMaxIdle(Integer.parseInt(pro.get("redis.maxIdle").toString()));//最大闲置个数
		poolConfig.setMinIdle(Integer.parseInt(pro.get("redis.minIdle").toString()));//最小闲置个数
		poolConfig.setMaxTotal(Integer.parseInt(pro.get("redis.maxTotal").toString()));//最大连接数
		pool = new JedisPool(poolConfig,pro.getProperty("redis.url") , Integer.parseInt(pro.get("redis.port").toString()));
	}

	//获得jedis资源的方法
	public static Jedis getJedis(){
		return pool.getResource();
	}
	
	public static void main(String[] args) {
		Jedis jedis = getJedis();
		System.out.println(jedis.get("xxx"));
	}	
}

开启Redis服务【注意不要关闭控制台窗口,否则服务会停止运行】
运行main方法如果控制台输出null即为配置成功!
③使用时用类名直接调用getJedis()方法去初始化一个Jedis对象
Jedis jedis = JedisPoolUtils.getJedis();
往缓存服务器存值直接调用set方法
jedis.set("jsonlist", jsonlist);
取值用get方法
String json = jedis.get("json_list");

有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

spring的自我理解【IOC、DI】

liaoshengzhe阅读(4725)

说起spring框架,我认为严格意义上来说spring更像是一个粘合剂【纯属个人观点】。一般都叫spring容器。因为它粘合了别的框架或者组件。所以spring是个容器。程序开发的模块组件都可以通过spring这个容器进行组装拼合,spring为我们提供了很多管理功能。而且,它是一个轻量级的容器。【当spring启动时,创建和销毁的资源都非常少】它的侵入性非常小,也可以说是耦合性很低。可以很轻易的替换掉它,而不需要做太大的改动,它对别的组件的依赖非常小。

fe5cf4ed04074245afb47fdaab9b00f0-image.png

谈到spring必然要想到IOC(Inversion of Control)【控制反转】,它不是一门技术,而是一种设计思想。在java开发中,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。先要明确谁控制谁,控制什么。
在传统的java SE设计中我们直接在对象内部通过new进行对象的创建,是程序主动去创建依赖对象;而IOC是有一个专门的容器来创建这些对象,即有IOC容器来控制对象的创建;

何为反转?有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

举一个通俗易懂的例子:比如芈月传这个电视剧的剧本,如果编剧在写剧本的时候把芈月直接写为孙俪,直接围绕孙俪这个对象去编剧本,这种方式就相当于传统的java SE的设计思想。而当有了IOC思想之后,编剧写剧本的时候直接写芈月说了什么话,做了什么事直接围绕戏剧人物进行刻画描述,而找演员(创建对象)这种事情交给导演去做。而不是剧本直接定死的,即使孙俪档期冲突了,也可以找王丽,李丽之类的。这就是我对控制反转的个人见解。

说完IOC之后,接下来说一下DI(Dependency Injection)【依赖注入】:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器

●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

再举一个简单的例子
所谓依赖,从程序的角度看,就是比如A要调用B的方法,那么A就依赖于B,反正A要用到B,则A依赖于B。
所谓倒置,你必须理解如果不倒置,会怎么着,因为A必须要有B,才可以调用B,如果不倒置,意思就是A主动获取B的实例:B b = new B(),这就是最简单的获取B实例的方法(当然还有各种设计模式可以帮助你去获得B的实例,比如工厂、Locator等等),然后你就可以调用b对象了。
所以,不倒置,意味着A要主动获取B,才能使用B;到了这里,你就应该明白了倒置的意思了。倒置就是A要调用B的话,A并不需要主动获取B,而是由其它人自动将B送上门来。

形象的举例就是:

通常情况下,假如你有一天在家里饿了,要吃饭,那么你可以到你小区外面的饭店去,告诉他们,你需要一份黄焖鸡米饭,然后小卖部给你做一份黄焖鸡米饭!

这本来没有太大问题,关键是如果饭店很远,那么你必须知道:从你家如何到饭店;饭店里的黄焖鸡米饭是否已经售罄;你还要考虑是否开着车去;等等等等,也许有太多的问题要考虑了。也就是说,为了吃上一顿黄焖鸡米饭,你还可能需要依赖于车等等这些交通工具或别的工具,问题是不是变得复杂了?那么如何解决这个问题呢?

解决这个问题的方法很简单:饿了么应运而生,凡是饿了么的会员,你只要告知饿了么app你需要什么,骑手将主动把货物给你送上门来!这样一来,你只需要做两件事情,你就可以活得更加轻松自在:
第一:向饿了么注册为会员
第二:在饿了么上面下单

是不是和Spring的做法很类似呢?Spring就是饿了么、饭店,你就是A对象,黄焖鸡米饭就是B对象
第一:在Spring中声明一个类:A
第二:告诉Spring,A需要B

假设A是UserAction类,而B是UserService类

 

在Spring这个饿了么app(工厂)中,有很多对象/服务:userService,documentService,orgService,也有很多会员:userAction等等,声明userAction需要userService即可,Spring将通过你给它提供的通道主动把userService送上门来,因此UserAction的代码示例类似如下所示:

package org.leadfar.web;
public class UserAction{
     private UserService userService;
     public String login(){
          userService.valifyUser(xxx);
     }
     public void setUserService(UserService userService){ 
          this.userService = userService;
     }
}

在这段代码里面,你无需自己创建UserService对象(Spring作为背后无形的手,把UserService对象通过你定义的setUserService()方法把它主动送给了你,这就叫依赖注入!)

所以个人总结下来就是
ioc 相当于 平时我们使用的工厂。
di 相当于 平时我们的setter方法。

有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

spring的自我理解【AOP】

liaoshengzhe阅读(4470)

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用”横切”技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP核心概念

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

对连接点进行拦截的定义

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

总的来看AOP相当于平时我们的代理模式。

有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

单例设计模式

liaoshengzhe阅读(4199)

单例设计模式是23种设计模式中最简单的一种模式。它要有以下几个要素:
一、私有的构造方法
二、指向自己实例的私有静态引用
三、以自己实例为返回值的静态公有方法

单例模式根据实例化对象时机的不同分为两种:一种是恶汉式单例,一种是懒汉式单例。前者是在单例类被加载时候就实例化一个对象交给自己的引用;而后者是在调用取得实例方法的时候才会实例化对象。代码如下:
饿汉式单例


public class Singleton {  
    private static Singleton singleton = new Singleton();  
    private Singleton(){}  
    public static Singleton getInstance(){  
        return singleton;  
    }  
}

懒汉式单例


public class Singleton {  
    private static Singleton singleton;  
    private Singleton(){}  
      
    public static synchronized Singleton getInstance(){  
        if(singleton==null){  
            singleton = new Singleton();  
        }  
        return singleton;  
    }  
}

单例模式的优点:

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景:由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

单例模式注意事项:

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

关于java中单例模式的一些争议:

单例模式的对象长时间不用会被jvm垃圾收集器收集吗

看到不少资料中说:如果一个单例对象在内存中长久不用,会被jvm认为是一个垃圾,在执行垃圾收集的时候会被清理掉。对此这个说法,笔者持怀疑态度,笔者本人的观点是:在hotspot虚拟机1.6版本中,除非人为地断开单例中静态引用到单例对象的联接,否则jvm垃圾收集器是不会回收单例对象的。

在一个jvm中会出现多个单例吗

在分布式系统、多个类加载器、以及序列化的的情况下,会产生多个单例,这一点是无庸置疑的。那么在同一个jvm中,会不会产生单例呢?使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例。代码如下


Class c = Class.forName(Singleton.class.getName());  
Constructor ct = c.getDeclaredConstructor();  
ct.setAccessible(true);  
Singleton singleton = (Singleton)ct.newInstance();

懒汉式单例线程安全吗

主要是网上的一些说法,懒汉式的单例模式是线程不安全的,即使是在实例化对象的方法上加synchronized关键字,也依然是危险的,但是笔者经过编码测试,发现加synchronized关键字修饰后,虽然对性能有部分影响,但是却是线程安全的,并不会产生实例化多个对象的情况。

单例模式只有饿汉式和懒汉式两种吗

饿汉式单例和懒汉式单例只是两种比较主流和常用的单例模式方法,从理论上讲,任何可以实现一个类只有一个实例的设计模式,都可以称为单例模式。

单例类可以被继承吗

饿汉式单例和懒汉式单例由于构造方法是private的,所以他们都是不可继承的,但是其他很多单例模式是可以继承的,例如登记式单例。

饿汉式单例好还是懒汉式单例好

在java中,饿汉式单例要优于懒汉式单例。

有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

java实现邮件发送

liaoshengzhe阅读(3464)

大网站的注册基本都要验证邮箱或者手机号那么怎么用java实现邮件验证呢?

一、我们需要一个必要的jar包【mail.jar】mail.jar【下载后请自行改名】
二、我们需要一个发送邮件的工具类【简单了解,做到会使用即可】MailUtils.java

import java.io.UnsupportedEncodingException;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;

public class MailUtils {

	public static void sendMail(String email, String emailMsg)
			throws AddressException, MessagingException, UnsupportedEncodingException {
		// 1.创建一个程序与邮件服务器会话对象 Session

		Properties props = new Properties();
		props.setProperty("mail.transport.protocol", "SMTP");//发送协议SMTP
		//邮件发送服务器我用的是163邮箱,所以是smtp.163.com。其他的邮箱请去官网查询
		props.setProperty("mail.host", "smtp.163.com");
		props.setProperty("mail.smtp.auth", "true");// 指定验证为true

		// 创建验证器
		Authenticator auth = new Authenticator() {
			public PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication("iamliaoshengzhe@163.com", "这个地方填写第三方邮件客户端的授权码");
				//163的登网页版邮箱去设置授权码,QQ邮箱在设置里面中打开SMTP服务后面可以获取授权码
			}
		};

		Session session = Session.getInstance(props, auth);
		
		// 2.创建一个Message,它相当于是邮件内容
		Message message = new MimeMessage(session);

		message.setFrom(new InternetAddress("iamliaoshengzhe@163.com","这个是邮件的发送者昵称,随意设置即可")); // 设置发送者

		message.setRecipient(RecipientType.TO, new InternetAddress(email)); // 设置发送方式与接收者

		message.setSubject("用户激活");

		message.setContent(emailMsg, "text/html;charset=utf-8");

		// 3.创建 Transport用于将邮件发送
		Transport.send(message);
	}
}

三、在servlet中调用即可
MailUtils.sendMail(user.getEmail(), emailMsg);
第一个参数是前台注册者填写的email地址,第二个参数是邮件内容按照自己的喜好去设置!

有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

java中list转化为json

liaoshengzhe阅读(2975)

java中list转化为json的方式一般用以下两种
一、gson的方式
①首先要导入所需要的jar包【gson-2.2.4.jar】gson-2.2.4.jar【下载后自行改名】
②转化方式如下

List<User> list=cs.selectAllUser();//例如从数据库查询的所有user对象的集合放到list集合中
		Gson gson=new Gson();
		String jsonList=gson.toJson(list);//用toJson方法

二、json-lib的方式【暂不提供下载,请善用百度】
①同样首先要导入jar包【主要json-lib-2.4-jdk15.jar】等5个jar包
②转化方式如下

List<User> list=cs.selectAllUser();
		JSONArray.fromObject(list).toString();//直接调用静态方法fromObject即可
		
有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

java的反射

liaoshengzhe阅读(544)

1. 反射机制:在程序运行期动态获取其类型信息的机制。

2. java.lang.Class:代表正在运行的类和接口的类型信息对象。

3. 获取Class对象的方式:
1) 通过Object类提供的Class getClass()方法。
Integer integer = Integer.valueOF(12);
Class clazz = integer.getClass();
2) 通过Class类提供的静态方法forName(String 类名)来获取。
Class clazz = Class.forName(“java.lang.Integer”);
3) 通过类的静态属性class也可以获取: Class clazz = Integer.class;

4. 通过反射获取某个类型的属性、方法、构造方法,父类和实现的接口。
1) 获取类或接口的属性信息:用java.lang.reflect.Field代表。
public Field[] getFields(); 返回一个包含公有字段对应的Field对象数组
public Field getField(String name) throws NoSuchFieldException, SecurityException; 获取指定名称的公有字段对应的Field对象
public Field[] getDeclaredFields() throws SecurityException 返回一个包含的所有字段对应的Field对象数组
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;

2) 获取类或接口的方法:用java.lang.reflect.Method代表。
public Method[] getMethods() throws SecurityException; 返回一个包含公有 Method对象的数组
public Method getMethod(String name, Class… parameterTypes) throws NoSuchMethodException, SecurityException
返回指定方法名和指定参数信息的Method对象。

3) 获取类的构造方法:用java.lang.reflect.Constructor代表。
public Constructor[] getConstructors() throws SecurityException
public Constructor getConstructor(Class… parameterTypes) throws NoSuchMethodException, SecurityException

4) 获取方法的参数类型和返回值类型:调用Method类提供的对应方法。
public Class[] getParameterTypes(); 按照声明顺序返回 Class 对象的数组
public Class getReturnType();

5. 通过反射来创建该类型的对象:通过Class提供的newInstance()方法调用默认的构造方法来创建该类型的一个对象。
Object obj = Class.forName(“java.util.Date”).newInstance();

6. 通过反射访问某个对象的属性或方法:
Field field = clazz.getDeclaredField(“属性名”); //访问属性
field.setAccessible(true); //取消Java语言访问检查
Object obj = field.get(该类型的某个对象); //获取指定对象上该属性的值
field.set(该类型的某个对象, Object 值); //将指定对象变量上此 Field对象表示的字段设置为指定的新值。

 Method method = clazz.getDeclaredMethod("方法名", 参数类型信息); //访问指定方法
 method.setAssessible(true);  //取消Java语言访问检查
 Object obj = method.invoke(Object 该类型的某个对象, Object... 实参列表);  //对带有指定参数的指定对象调用由此 Method对象表示的底层方法

7. 通过反射调用带参的构造方法比较麻烦。很多应用到反射技术的高级框架都要求你的类提供一个不带参数的构造方法。
大多数的高级框架的核心技术都是:XML做配置,并解析,然后用反射来创建对象。

有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

JAVA中如何将以Date型的数据保存到数据库以Datetime型的字段中

liaoshengzhe阅读(264)

MySQL里面有个数据类型Datetime存放的就是日期+时间的格式,比如:
 1900-1-1 16:36:44.000
 如果要将1900-1-1 16:36:44存入数据库中(即上面的Datetime字段),可以通过转换生成对应的Date数据就行。
 插入数据库的数据必须是java.sql.Date类型的日期,所以你可以通过类似你上面的处理将字符串转成java.sql.Date类型的日期再存入数据库就成。
 下面给你一个将字符串1900-1-1 16:36:44改变成一个java.sql.Date类型的一个class。

 import java.text.*;
 import java.util.Locale;
 public class StringToDate {
 public final static java.sql.Date string2Date(String dateString)
 throws java.lang.Exception {
 DateFormat dateFormat;
 dateFormat = new SimpleDateFormat(“yyyy-MM-dd kk:mm:ss”, Locale.ENGLISH);
 dateFormat.setLenient(false);
 java.util.Date timeDate = dateFormat.parse(dateString);//util类型
 java.sql.Date dateTime = new java.sql.Date(timeDate.getTime());//sql类型
 return dateTime;
 }
 }
方法二
 SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd kk:mm:ss”);
 java.util.Date now = new java.util.Date();
 String resultDate = sdf.format(now);
 java.util.Date last = new java.util.Date(resultDate);
 …..
 psmt.setDate(i, new java.sql.Date(last)) ;
有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

java中对Date类型数据进行加减操作

liaoshengzhe阅读(256)

import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;

public class TestDate2 {
 public static void main(String[] args) throws Exception {

    Date date=new Date();//定义一个时间
    Calendar resultDate=Calendar.getInstance();//定义一个Calendar对象
    resultDate.setTime(date);
    resultDate.add(Calendar.YEAR,-1);//增加年份 值为负数即为减
    resultDate.add(Calendar.MONTH,3);//操作月份
    resultDate.add(Calendar.DAYOFYEAR,+10);//操作当年的第几天
    //后续的Calendar.所有方法基本用法一致  看名字就能知道操作的是年月日或时分秒
    Date date1=resultDate.getTime();  
    SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd"); 
    String result1=format.format(date);
    String result2=format.format(date1);
    System.out.println("刚开始定义的时间:"+result1);
    System.out.println("操作后的时间:"+result2);
}


}
有一种落差是,你的才华配不上梦想,你的能力配不上野心,也辜负了所受的苦难。

友情链接

我的主页张少脆的博客