JavaWeb学习篇之----Session&&Cookie
今天继续来看看JavaWeb的相关知识,这篇文章主要来讲一下Session和Cookie的相关知识,首先我们来看一下Cookie的相关知识:
一、Cookie
简介:
//显示上次访问的时间
public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
//设置编码
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("你上一次访问的时间是:"); //获得用户的时间cookie,并且获取值,如果第一次的话,是没有Cookie信息的,所以Cookie数组可能为null,所以我们要做判断
Cookie cookies[] = request.getCookies();
for(int i=0;cookies != null && i<cookies.length;i++){
if(cookies[i].getName().equals("lastAccessTime")){
long cookieValue = Long.parseLong(cookies[i].getValue());
Date date = new Date(cookieValue);
out.print(date.toLocaleString());
}
} //创建每次访问的时候,我们都会回写一个Cookie给客户机,并且将Cookie的有效期设置为30天,路径设置成整个web应用
Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
cookie.setMaxAge(30*24*3600);
cookie.setPath("/ServletDemo");
response.addCookie(cookie); }
在doGet方法中调用这个方法测试一下:
//删除Cookie信息
public void test1(HttpServletRequest request,HttpServletResponse response){
//下面是删除cookie
Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
cookie.setMaxAge(0);//将有效期设置成0
cookie.setPath("/ServletDemo");//设置的路径必须要和之前的一样,否则是删除不了的
response.addCookie(cookie);
}
我们在次访问ServletCookie的时候,这时候还是会携带一个Cookie信息的,这个Cookie信息是上一次的,此时服务器还是会回写一个有效期为0的Cookie给客户机
这样我们就成功删除了lastAccessTime这个Cookie的信息
我们在来总结一些Cookie的一些细节:
一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。
注意,删除cookie时,path必须一致,否则不会删除
二、Session
Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。
同时Session也是一个域对象,我们之前介绍了两个域对象:ServletContext和Request,那么Session域的作用范围是:默认情况下是在一个会话期间,当然这个范围我们是可以设置的,设置之后可以在多个会话之间。那么Session的生命周期是:
1.Session什么时候创建:
某server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建。
2.Session什么时候销毁:
Session在下列情况下被删除:
A.程序调用HttpSession.invalidate()
B.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
C.服务器进程被停止
再次注意关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效。
下面在来看一下Session技术原理:
下面在来看一下Session的相关api:
getAttribute(String name)/getAttributeNames()/setAttribute(String name)/removeAttribute(String name):这些方法都是和Session域中的值有关的方法,和之前的ServletContext,Request域的是一样的
getCreationTime():获取Session的创建时间
getId():获取session的id
getServletContext():获取ServletContext对象
invalidate():删除session的方法,就是将session设置成无效的
setMaxInactiveInterval(int interval):这个方法设置session的最大有效时间
下面来看一下Session的第一个例子:
HttpSession session = request.getSession();
System.out.println("SessionObject:"+session);
System.out.println("SessionId:"+session.getId());
打印Session的对象和ID
我们定义两个Servlet,将上面的代码拷贝到Servlet中,访问第一个Servlet1,然后再访问第二个Servlet2,我们看一下打印结果:
运行结果:
SessionObject:org.apache.catalina.session.StandardSessionFacade@7244001e
SessionId:96F3F15432E0660E0654B1CE240C4C36
SessionObject:org.apache.catalina.session.StandardSessionFacade@7244001e
SessionId:96F3F15432E0660E0654B1CE240C4C36
我们可以看到我访问了两个不同的Servlet,但是通过getSession()方法获取到的Session是同一个,所以getSession()这个方法内部的执行过程是先看有没有Session,如果有就不创建了,没有的话就直接创建。
其实request还有一个方法:getSession(boolean mode)
如果mode是true的话,效果是和getSession()方法的效果一样的
如果mode是false的话,效果是:如果有Session就返回,没有的话,就不创建session,而返回一个null
上面我们看到了Session的生命周期,session销毁的时机并不是关闭浏览器,而是在用户在一段时间内(默认是30分钟)不使用了,就自动销毁的,比如:一个人打开一个浏览器,开启一个session,这时候他离开了超过30分钟,等再回来操作的时候,session就被销毁了,当然这个有效时间是可以设置的,上面讲到了两种方法:
一种是通过web.xml文件中配置(单位是分钟):
<session-config>
<session-out>10</session-out>
</session-config>
另一种是通过代码设置(单位是秒):
session.setMaxInactiveInterval(30*60);
那么我们getSession()方法是怎么判断Session是存在的呢?其实Session的实现原理是基于Cookie的技术实现的,每次我们在创建一个session的时候,都会有一个对应的sessionid,然后会将这个sessionid信息写入到cookie中,但是这个cookie的有效时间是这个浏览器进程中,即这个cookie是在服务器的内存中的,不会回写给客户机缓存到磁盘中。所以我们会在一个浏览器中使用同一个session的,当我们关闭浏览器的时候,这个cookie就被销毁了,那么sessionid就没有了,当我们在打开浏览器的时候,服务端又会重新创建一个session。所以我们会误认为session的生命周期是浏览器进程。当用户关闭了浏览器虽然session
cookie已经消失,但session对象仍然保存在服务器端
假如现在有一个人买了几千本书,准备去付款,但是不小心把浏览器关了,这时候在打开浏览器的时候就会发现之前买的书都不在了,这种情况是很严重的,所以我们要改善这种情况,修改的原理很简单,我们知道那个sessionid是写在cookie中的,但是这个cookie默认的情况下只存在服务器的内存中的,不会回写到客户机浏览器的缓存中。如果我们要是修改的话,只需要修改这个cookie的有效期,将这个cookie回写到客户机浏览器中即可。
HttpSession session = request.getSession();
String sessionId = session.getId();
Cookie cookie = new Cookie("JSESSIONID",sessionId);//把系统的session id的覆盖掉
cookie.setPath("/ServletDemo");
cookie.setMaxAge(30*360);//30分钟,因为session的生命周期是30分钟
response.addCookie(cookie);
默认携带sessionid信息的cookie字段是:JESSIONID
我们可以覆盖这个信息的,将有效期设置成30分钟,因为默认的session的有效期是30分钟,所以如果将cookie的有效期大于30分钟的话,是没有意义的,因为当超过30分钟之后,session就销毁了,即使携带cookie过去,也是找不到对应的session了,但是我们也是可以设置session的有效时间的。所以我们最好将这个cookie的有效期设置要不超过session的有效期,不然是没有意义的。运行之后,我们可以查看浏览器中的Cookie信息,这里使用IE浏览器,点击F12键,选择缓存-》查看Cookie信息:
上面的就将sessionid的信息以cookie的形式回写到客户机缓存中,当我们关闭浏览器中,在开启浏览器的话,还是可以访问到我们之前的数据的,其实这也就实现了多个浏览器之间共享一个session的机制。
但是现在还有一个问题,如果用户手贱他把浏览器设置成禁止Cookie了,那么又悲剧了,我们服务端写给客户机的Cookie,但是客户机是不接受的,所以这样也是会丢失数据的。那么这时候我们又该怎么办呀?
一种新技术又产生了,那就是URL重写:
我们首先来看一下,用户禁止Cookie之后,我们会发现在同一个浏览器进程中,访问不同的Servlet,数据也是没有的:
我们这里定义三个Servlet:IndexServlet,BuyServlet,PayServlet
模拟的场景就是:IndexServlet是首页,里面写入连个超链接,分别链接到购买物品Servlet:BuyServlet,和付款Servlet:PayServlet,代码如下:
IndexServlet:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
req.getSession();
String buyUrl = "/ServletDemo/BuyServlet";
String payUrl = "/ServletDemo/PayServlet";
resp.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");
resp.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");
}
BuyServlet:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
//购买了一件物品,将这个物品存入到session中,然后将这个sessionid回写到客户机,有效时间是30分钟
HttpSession session = req.getSession();
session.setAttribute("store", "air-confication");
Cookie cookie = new Cookie("JSESSIONID",session.getId());//把系统的session id的覆盖掉
cookie.setMaxAge(30*360);
cookie.setPath("/ServletDemo");
resp.addCookie(cookie);
}
PayServlet:
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//我们从session中拿去商品进行显示
HttpSession session = request.getSession();
response.getWriter().print("you buy store is:"+session.getAttribute("store"));
}
我们试验禁止浏览器的Cookie:
使用IE浏览器:选择工具-》Internet选项-》隐私-》高级-》禁止Cookie
这里有一个问题要注意,浏览器使用localhost域名,阻止Cookie是没有效果的,这个问题纠结了好长时间!我们需要直接使用:127.0.0.1来测试:http://127.0.0.1:8080/ServletDemo/IndexServlet
我们点击Buy,这时候会回写一个sessionid,但是客户机禁止Cookie了,所以浏览器没有缓存sessionid,不信的话我们可以查看浏览器中缓存的Cookie信息,是没有我们的Cookie的。
然后我们返回在点击Pay的时候发现,购买的东东为null
这个我们在同一个浏览器中都发现购买的东西都不见了,这个很蛋疼的,我们之前不回写sessionid,即使我们禁止Cookie的话也是没有影响的,因为那个Cookie是在浏览器进程中的,不会回写,所以禁止Cookie没有影响。但是我们现在是回写了sessionid的Cookie,所以这个问题我们得解决,上面说过了使用URL重写技术,其实这个技术很简单的,就是将网站中的所以url后面会携带一个sessionid,这样就可以保证当禁止Cookie的时候,我们还是能够得到sessionid,然后找到相应的session。测试:
这里只需要修改IndexServlet中的代码:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
resp.setDateHeader("expires", 0);
req.getSession();
String buyUrl = resp.encodeURL("/ServletDemo/BuyServlet");
String payUrl = resp.encodeURL("/ServletDemo/PayServlet");
resp.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");
resp.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");
}
一定要执行req.getSession()这个方法,来生成一个session,不然encodeURL中是不会得到sessionid的
这里就是重写了,当然我们直接使用encodeURL这个方法,而不是直接自己去将sessionid拼接到url中
访问IndexServlet:
我们可以看到响应信息是我们回写客户机显示的连接,url后面是会携带jsessionid字段的
这样,即使用户禁止了Cookie,也是可以实现数据保留的。但是我们也看到了URL重写的弊端,对于每个url都必须重写,这个是很麻烦的,而且还有一个问题,针对URL重写技术,当我们关闭浏览器的时候,再去访问的时候还是失败,因为我们关闭浏览器,再去打开浏览器访问IndexServlet的时候,会重新创建一个session,这时候在将sessionid重写到url中,所以不是之前的session了,至此,也知道了,URL重写是做不到多个浏览器进程之间的session数据共享的,那么这个问题我们需要去该吗?这个是没办法修改的,只能做到这一步了。
下面在来看一下上面内容的一些细节:
当我们在使用URL重写技术的时候,我们把浏览器中的Cookie不禁止,即保留Cookie和URL重写这两种技术
我们会发现当用户携带Cookie过来的时候,url链接之后是没有jsessionid字段了,这个得益于encodeURL方法中做了判断
所以优先级是Cookie中的sessionid高
下面我们在来通过一个实例来总结一下所讲的内容
实例:防止表单重复提交
方法一:前端使用JS技术
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head> <title>My JSP '1.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page"> <script type="text/javascript">
var iscommitted = false;
function dosubmits(){
if(!iscommitted){
iscommitted = true;
return true;
}else{
return false;
}
}
</script> <script type="text/javascript">
function dosubmit(){
var input = document.getElementById("submit");
input.disabled = 'disabled';
return true;
}
</script> </head> <body>
<form action="/ServletDemo/FormResubmit1" method="post" onsubmit="return dosubmits()">
<input type="text" name="username">
<input id="submit" type="submit" value="提交">
</form>
</body>
</html>
我们定义了两个js函数,这两个js函数都是可以实现防止表单的重复提交
dosubmits()函数是在用户提交一次之后就通过一个变量来控制不允许提交
dosubmit()函数是在用户提交一次之后就将提交按钮设置成不可点击
但是使用上面的这种方法来防止表单重复提交是有问题的,首先客户是可以通过查看文件的源代码,然后修改的代码的
而且,这种方式解决不了一些问题,比如:当用户点击提交之后,他立刻点击刷新按钮或者回退按钮,然后又可以提交了,所以说前端技术是不能彻底防止表单的重复提交,但是我们还是要这么做的,因为这样做至少还是能起到一定的防止效果的,当然我们还需要在服务器端做处理的,实现原理是:
服务器端首先产生一个随机数,标记一个唯一的表单,然后将这个随机数写入到session域中保存,同时我们将这个随机数使用转发技术携带给一个jsp/html表单中,在使用隐藏标签将这个随机数携带给提交的servlet中,然后再这个servlet中拿到这个隐藏标签的参数值,和我们之前写入到session域中的那个随机数进行比较,正确的话,说明是第一次提交,我们这时候就将那个随机数从session域中删除,当用户再一次点击提交表单的时候,再来判断的时候,因为session域中没有了这个随机数,所以是提交失败的
下面来看一下实现代码:
IndexServlet:
package com.weijia.servletsession; import java.io.IOException;
import java.security.MessageDigest;
import java.util.Random; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import sun.misc.BASE64Encoder; //表单重复提交
public class IndexServlet extends HttpServlet{ private static final long serialVersionUID = 1L; @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
doPost(req,resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
resp.setDateHeader("expires", 0);
try{
test(req,resp);
}catch(Exception e){
e.printStackTrace();
}
} //产生表单
public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
//产生表单号
String token = TokenProcessor.generateToken();
request.getSession().setAttribute("token", token);
request.getRequestDispatcher("/form.jsp").forward(request, response);
} } //单例模式,一个人报数比多个人报数重复率底
class TokenProcessor{ private static final TokenProcessor instance = new TokenProcessor(); private TokenProcessor(){ } public static TokenProcessor getInstance(){
return instance;
} public static String generateToken(){
String token = System.currentTimeMillis() + new Random().nextInt() + "";
try{
MessageDigest md = MessageDigest.getInstance("md5");
byte[] md5 = md.digest(token.getBytes());
//base64
//0011 0010 1100 1101 0010 1001
//00001100 00101100 00110100 00101001:最小是0,最大的数是63,共64个数
//base码表
//数据传输的作用
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
}catch(Exception e){
return null;
}
} }
这里面我们定义了一个令牌发生器(随机数产生器),使用的是单例模式
但是有一个问题就是:使用Random类产生的随机数的长度是不一定的,我们现在想让每次得到的随机数的长度是一样的,此时我们会想到MD5,首先每个数据的MD5是唯一的,还有MD5的长度是一定的128位(16Bytes),但是还有一个问题那个MD5是一个字节数组,当我们将其转化成String类型的时候,不管使用什么码表,都会出现乱码的问题,在随机数中出现了乱码,那就等于是死了,所以我们需要解决这个问题,这时候我们想到了BASE64编码,因为我们知道任何字节数据通过BASE64编码之后生成的字节数据的大小是在0~63之间的(原理很简单,就是将字节数组从左到右,每六位是一组,然后再高位补足两个0,那么这个字节大小的范围就是在0~63了),同时BASE64有一套自己的码表,里面只有大小写字母和数字,这样就不会产生乱码了。
下面在来看一下我们转发的form.jsp中的表单定义:
<form action="/ServletDemo/FormResubmit" method="post">
<input type="hidden" name="token" value="${token}">
用户名:<input type="text" name="username"><br/>
<input type="submit" value="提交">
</form>
这里我们使用了隐藏标签hidden,同时也使用了el表达式来取出之前存入到session域中的token字段值,
下面再来看一下处理这个表单的Servlet:FormResubmit
package com.weijia.servletsession; 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 FormResubmit extends HttpServlet{ @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
doPost(req,resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
boolean flag = isTokenValid(req);
if(!flag){
System.out.println("请不要重复提交");
return;
}
System.out.println("向数据库中注册用户--------");
req.getSession().removeAttribute("token"); } //验证表单提交是否有效,返回true,表示表单可以提交
public boolean isTokenValid(HttpServletRequest request){
//首先判断传递过来的表单号是否有效
String clientToken = request.getParameter("token");
if(clientToken == null){
return false;
} //然后再判断服务器端session域中时候又令牌信息了
String serverToken = (String)request.getSession().getAttribute("token");
if(serverToken == null){
return false;
} //在比较表单携带过来的随机数和session域中的令牌信息是否一致
if(!clientToken.equals(serverToken)){
return false;
} return true;
} }
这样我们就可以防止表单的重复提交了,其实上面的实现原理就是struts框架中防止表单重复提交的原理
总结:这一篇主要讲解了Session和Cookie的相关知识,到此关于JavaWeb的知识,大体上说的差不多了,后面只剩下一个JSP和标签库的相关知识了,这两篇文章会在后面更新。
JavaWeb学习篇之----Session&&Cookie的更多相关文章
- [转载]JavaEE学习篇之——Session&&Cookie
原文链接: http://blog.csdn.net/jiangwei0910410003/article/details/23337043 今天继续来看看JavaWeb的相关知识,这篇文章主要来讲一 ...
- JavaWeb学习篇之----自定义标签&&JSTL标签库详解
今天来看一下自定义标签的内容,自定义标签是JavaWeb的一部分非常重要的核心功能,我们之前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片段,下面 ...
- 超全面的JavaWeb笔记day11<JSP&Session&Cookie&HttpSession>
1.JSP 2.回话跟踪技术 3.Cookie 4.HttpSession JSP入门 1 JSP概述 1.1 什么是JSP JSP(Java Server Pages)是JavaWeb服务器端的动态 ...
- JavaWeb学习篇之----web应用的虚拟目录映射和主机搭建(Tomcat)
从今天开始来学习JavaWeb的相关知识,之前弄过一段时间JavaWeb的,就是在做毕业设计的时候搞过,但是那时候完全是为了任务去学习,所以效果不好,好多东西都没有深入的研究过,所以接下来的一段时间我 ...
- JavaWeb学习篇之----容器Response详解
今天在来看一下Response容器的相关知识,其实这篇blog早就应该编写了,只是最近有点忙,所以被中断了.下面我们就来看一下Response容器的相关知识吧.Response和我们即将在后面说到的R ...
- JavaWeb学习篇之----HTTP协议详解
简介: HTTP是hypertexttransfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程. HTTP协 ...
- 【JAVAWEB学习笔记】16_session&cookie
会话技术Cookie&Session 学习目标 案例一.记录用户的上次访问时间---cookie 案例二.实现验证码的校验----session 一.会话技术简介 1.存储客户端的状态 由一个 ...
- JavaWeb学习篇之----EL表达式详解
我们之前的几篇文章中都提到了一个EL表达式,那么这个EL表达式到底是什么东东呢?为什么用处那么大,下面我们就来看看EL表达式的相关内容 EL表达式简介: EL 全名为Expression Langua ...
- JavaWeb学习篇之----容器Request详解
前篇说到了Response容器对象,这篇我们就来看一下Request容器对象,之前也说过了,这个两个容器对象是相对应的,每次用户请求服务器的时候web容器就会给创建这对容器对象,他们是共存亡的,当然R ...
随机推荐
- React 使用antd 清空表单
handleResetClick = e => { this.props.form.resetFields();};
- poj 2752 kmp的next数组
题目大意: 求一个字符串中某一个既是前缀又是后缀的前缀的结尾下标: 基本思路: 从_next[len]开始找_next[_next[len]],再找_next[_next[_next[len]]],一 ...
- vue组件 is ref
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 正规式α向有限自动机M的转换
[注:这一节是在学习东南大学廖力老师的公开课时,所记录的一些知识点截屏,谢谢廖力老师的辛劳付出] 引入3条正规式分裂规则来分裂α,所得到的是NFA M(因为包含ε弧,之后进行确定化就是所需要求得DF ...
- rest framework 之前
在开始rest framework之前,我们先来了解一下什么是restful rest 是一种软件架构风格,Representational state Transfer 它从资源的角度去看整个网络, ...
- push declined due to email privacy restrictions
使用git push到Github网站的时候提示: push declined due to email privacy restrictions 原因 在Github设置里有一个隐私选项 Block ...
- TCP三次挥手
tcp:三次握手 client和server之间需要经历三次握手才能建立连接(connnect()方法中封装了三次握手的步骤)syn:同步请求,建立连接的请求ack:对syn请求包的确认 应答syn: ...
- 如何将项目中的package.json文件中的模块更新到最新版本
有时候自己之前开发的项目回头运行的时候发行好多模块的版本不是最新的,此时想更为为最新版本,使用如下命令即可: npm i -g npm-check-updates ncu -u npm install
- CacheException: java.io.OptionalDataException
CacheException: java.io.OptionalDataException iro.authc.AbstractAuthenticator] - Authentication fail ...
- hive UDAF开发和运行全过程
介绍 hive的用户自定义聚合函数(UDAF)是一个很好的功能,集成了先进的数据处理.hive有两种UDAF:简单和通用.顾名思义,简单的UDAF,写的相当简单的,但因为使用Java反射导致性能损失, ...