* if (map.containskey(key) && map.get(key).equals(oldvalue)) {
* map.put(key, newvalue);
* return true;
* } else return false;
accesscontroller.doprivileged是一个在accesscontroller类中的静态方法,允许在一个类实例中的代码通知这个accesscontroller:它的代码主体是享受"privileged(特权的)",它单独负责对它的可得的资源的访问请求,而不管这个请求是由什么代码所引发的。
这就是说,一个调用者在调用doprivileged方法时,可被标识为 "特权"。在做访问控制决策时,如果checkpermission方法遇到一个通过doprivileged调用而被表示为 "特权"的调用者,并且没有上下文自变量,checkpermission方法则将终止检查。如果那个调用者的域具有特定的许可,则不做进一步检查,checkpermission安静地返回,表示那个访问请求是被允许的;如果那个域没有特定的许可,则象通常一样,一个异常被抛出。
一、"特权"特性的正常使用如下所示:
1、如果你不需要从"特权"块内返回一个值,按下列代码去做:
somemethod() {
privilegedaction是一个接口,它带有一个被称为run的方法,这个方法返回一个object。上述例子显示了一个用来实现那个接口的匿名内类的创建,并提供了一个run方法的具体实现。
当做一个doprivileged调用时,一个privilegedaction实现的实例被传递给它。doprivileged方法在使特权生效后,从privilegedaction实现中调用run方法,并返回run方法的返回值以作为doprivileged的返回值,这一点在本例中被忽略。
2、如果你需要返回一个值,你可按如下方法去做:
somemethod() {
3、如果用你的run方法执行的动作可能扔出一个"检查"的异常(包括在一个方法的throws子句列表中),则你需要使用privilegedexceptionaction接口,而不是使用privilegedaction接口:
有关被授予特权的一些重要事项:
首先,这个概念仅存在于一个单独线程内。一旦特权代码完成了任务,特权将被保证清除或作废。
第二,在这个例子中,在run方法中的代码体被授予了特权。然而,如果它调用无特权的不可信代码,则那个代码将不会获得任何特权;只有在特权代码具有许可并且在直到checkpermission调用的调用链中的所有随后的调用者也具有许可时, 一个许可才能被准予。
二、使用事例:
final string name = myclass.class.getname();
来自不同的位置的代码可以由一个codesource对象描述其位置和签名证书。根据代码的codesource的不同,代码拥有不同的权限。例如所有java sdk自带的代码都具有所有的权限,而applet中的代码则具有非常受限的权限,用户编写的代码可以自己定制权限(通过securitymanager)。
当执行一段代码时,这段代码的stacktrace包含了从main开始所有正在被调用而且没有结束的方法。在这个调用过程中,很有可能出现跨多个不同的codesource的调用序列。由于codesource不同,这些代码通常拥有不同的权限集。只有所有途经的codesource都具有对应的权限集合时,当前正在运行的代码才能存取某个resource。
而doprivileged方法是对这个规则的一种补充。他类似于unix中的setuid程序。unix中的login程序必须访问password文件从而获得用户授权信息,但是用户不能随意的访问password文件。因此,login程序具有setuid位,它不管被哪个用户所调用,都具有root的权限。
调用doprivileged的方法不管其stacktrace中其他方法的权限,而仅仅根据当前方法的权限来判断用户是否能访问某个resource。也即可以规定用户只能用某种预定的方式来访问其本来不能访问的resource。
使用doprivileged方法和使用setuid位都有需要注意的地方,例如仅执行必要的操作。否则,可能带来安全上的问题。
另一种稍微简单的方法:
对这种方法,sping还提供了listfactorybean, setfactorybean等类,这貌似是spring的一个可扩展框架,可以待以后进一步研究这个框架的实现方式。
最简单的一种方式就是直接用spring中提供的util包:
使用改方法时,xml文件头需要使用:
引用:
在使用java线程的时候,我们有时候要调用wait,notifyall,notify来等待或者唤醒线程,如果这几个方法没有包含在synchronized块中,将抛出illegalmonitorstateexception异常,并且当前线程被中断,为什么?
为什么?因为wait,notifyall,notify被调用的时候,都要使用到对象的监视器(锁),但是,如果这些方法不被包含在synchronized块中,那么当前线程就获取不到对象的锁,那么当我们wait的时候,wait根本不知道该释放哪个锁,所以就会抛出不合法的锁异常。
为什么?sleep不需要 被包含在synchronized块中呢?因为sleep不要释放锁,所以也就不抛出异常。
除去properites文件路径错误、拼写错误外,出现"could not resolve placeholder"很有可能是使用了多个propertyplaceholderconfigurer或者多个在j2se5.0后推出了自动装箱和拆箱的功能,以提高我们的开发效率,然而自动装箱和拆箱实际上是通过编译器来支持的(并非语言本身,或者说虚拟机),因而这种支持也隐藏了部分内部实质,再加上某些类的优化(比如integer里面的缓存等,参看关于缓存节),就更加容易在特定的环境下产生问题,并且如果不知道原来还无法调试。以下先是简单的介绍了编译器对装箱和拆箱的实现,并根据实现简单介绍一下可能会遇到的几个问题。
以下装箱和拆箱代码:
编译成字节码如下:
0 bipush 10
2 invokestatic java.lang.integer.valueof(int) : java.lang.integer [20]
5 astore_1 [value]
6 aload_1 [value]
7 checkcast java.lang.integer [21]
10 invokevirtual java.lang.integer.intvalue() : int [26]
13 istore_2 [intvalue]
14 new java.lang.integer [21]
17 dup
18 bipush 10
20 invokespecial java.lang.integer(int) [30]
23 astore_3 [newintvalue]
从以上字节码可以看到10首先调用valueof方法转换为integer实例,再赋值该value,而value强制转换成integer类后,会调用intvalue方法,后赋值给intvalue。这就是用编译器来实现装箱和拆箱。
查看以下代码:
可以编译通过,但是运行的时候却会发生nullpointerexception。这是由什么引起的呢?依然看一下字节码就可以了:
0 aconst_null
1 astore_1 [value]
2 aload_1 [value]
3 invokevirtual java.lang.integer.intvalue() : int [20]
6 istore_2 [intvalue]
从字节码中可以看到,从value赋值该intvalue事实上是直接在value实例上调用intvalue函数。
对当前代码,我们可以一眼就看出当前value是null的问题,但是如果这个null是在很远以外的地方赋值的呢?或者是间接赋值呢?这个时候遇到这种问题就会比较诡异了。
查看一下代码:
这段代码会是什么结果?
value1 == value2 is true
value3 == value4 is false
两段代码就是值不一样,其他的都一样,竟然会有区别?这个奥妙就因为装箱过程中调用的是valueof方法,而valueof方法对值在-128到127之间的数值缓存了(参见关于缓存一节),因而value1和value2的引用是相同的,而value3和value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。
这确的做法应该是:
system.out.println("value3 == value4 is " (value3.equals(value4)));
这样的结果就是预料的结果了:
value1 == value2 is true
value3 == value4 is true
所以我们要慎用“==”操作符。
在string中也有类似的情况,查看一下代码:
执行结果:
str1 == str2 is true
str3 == str4 is false
这是因为str1和str2使用的是同一个字符串,即在字符常量中的字符串,而str3和str4在使用字符常量中的字符为参数又创建出了两个新的字符串对象,因而在引用比较情况下是不等的。我们可以从字节码中得到这些信息(删除打印的代码):
0 ldc
2 astore_1 [str1]
3 ldc
5 astore_2 [str2]
6 new java.lang.string [22]
9 dup
10 ldc
12 invokespecial java.lang.string(java.lang.string) [24]
15 astore_3 [str3]
16 new java.lang.string [22]
19 dup
20 ldc
22 invokespecial java.lang.string(java.lang.string) [24]
25 astore 4 [str4]
正确的做法还是调用equals方法,而不是使用“==”操作符。
据目前信息,有缓存的类有:byte、short、integer、long以及boolean类。而这种缓存也只是在调用valueof(静态)方法的时候才会存在(装箱正是调用了valueof方法)。对整型,缓存的值都是-128到127(包括-128和127)之间,其他值都不缓存,而对boolean类型只有true和false值。代码如下:
问题重现:
今天一刚开始学java的同学在接触jsp的时候遇到了一个比较诡异的问题,他在jsp中始终无法使用自己写的类。简单的演示问题代码:
//没有定义包名
public class mydefaultclass {
}
//index.jsp文件
<%@ page language="java" contenttype="text/html; charset=iso-8859-1"
pageencoding="iso-8859-1"%>
doctype html public "-//w3c//dtd html 4.01 transitional//en" "http://www.w3.org/tr/html4/loose.dtd">
<html>
<head>
<title>insert title heretitle>
head>
<body>
<%
mydefaultclass cls = new mydefaultclass();
%>
load successful
body>
html>
出现的错误提示:
type exception report
message
description the server encountered an internal error () that prevented it from fulfilling this request.
exception
org.apache.jasper.jasperexception: unable to compile class for jsp:
an error occurred at line: 12 in the jsp file: /index.jsp
mydefaultclass cannot be resolved to a type
9:
10:
11: <%
12: mydefaultclass cls = new mydefaultclass();
13: %>
14:
15: load successful
an error occurred at line: 12 in the jsp file: /index.jsp
mydefaultclass cannot be resolved to a type
9:
10:
11: <%
12: mydefaultclass cls = new mydefaultclass();
13: %>
14:
15: load successful
stacktrace:
org.apache.jasper.compiler.defaulterrorhandler.javacerror(defaulterrorhandler.java:92)
org.apache.jasper.compiler.errordispatcher.javacerror(errordispatcher.java:330)
org.apache.jasper.compiler.jdtcompiler.generateclass(jdtcompiler.java:439)
org.apache.jasper.compiler.compiler.compile(compiler.java:349)
org.apache.jasper.compiler.compiler.compile(compiler.java:327)
org.apache.jasper.compiler.compiler.compile(compiler.java:314)
org.apache.jasper.jspcompilationcontext.compile(jspcompilationcontext.java:592)
org.apache.jasper.servlet.jspservletwrapper.service(jspservletwrapper.java:317)
org.apache.jasper.servlet.jspservlet.servicejspfile(jspservlet.java:313)
org.apache.jasper.servlet.jspservlet.service(jspservlet.java:260)
javax.servlet.http.httpservlet.service(httpservlet.java:717)
凯发天生赢家一触即发官网的解决方案:
为什么要特别提示他是刚开始接触java呢?因为我是想强调一下他没有定义包名的习惯,所以才会出现这个问题,而在java中不定义包名是一个非常不好的编程习惯。这个问题本身是可以通过定义自己的包名,然后在jsp文件中导入自己的包解决。
问题原因:
由于jsp文件在请求时动态的生成servlet类,然后动态的编译新生成的servlet类,并加载新编译出的servlet的class文件,运行相应的service方法,返回结果给客户端。根据栈信息可以知道问题出在了动态编译servlet类的时候,而且是说mydefaultclass无法被解析。
所以我猜测,可能是tomcat在解析jsp文件的时候,没有发现有用户导入的类型(没有使用命名空间,因而无法导入包,也不需要加包名的前缀,导致tomcat在解析jsp文件的时候无法自动感知到当前是引用了用户自定义的类的;也导致在编译的时候却能通过),所以在动态编译的时候没有设置classpath的值,然后导致了这个问题。然而对于这个“bug”,tomcat本身应该不会想不到,何况从逻辑上,默认的把/classes和/lib的目录加入到classpath中去编译动态生成的servlet类也是合理的,那么tomcat为什么要这样做呢?我的猜测可能会有两点吧:1. tomcat也认为不定义包名是非常不好的习惯,因而故意留下这个缺陷,以惊醒编程人员。2. 也有可能是出于性能的考虑,如果不加classpath应该是可以提升编译动态生成的servlet类的速度的,因而只要不需要classpath的情况下,默认就不加。
2010-09-22
在eclipse中的“run”中的“add java exception breakpoint…”项,选择要断住的抛出异常类型即可。
在这里选择的exception类型可以在breakpoint窗口中查看或取消:
2010-10-03
今天发现把jar文件或.class文件放在系统配置的classpath目录下,然而在eclipse的工程中却是无法加载的(通过classloader去加载相应的类)。最后我发现eclipse应该是重新设置了classpath的值了,也正因为这样它才能运行其bin目录下的文件。如打开工程目录下的.classpath文件:
然而如果加入一下内容:
重启eclipse,就可以正确的加载classpath中的内容了。
2011-09-16
首先来比较两段代码所产生的中间代码:
public class appconfig {
debugcode的中间代码(部分):
public class org.levin.insidejvm.miscs.debugcode {
public static void main(java.lang.string[] args);
0 getstatic java.lang.system.out : java.io.printstream [16]
3 ldc
5 invokevirtual java.io.printstream.println(java.lang.string) : void [24]
8 return
}
releasecode中间代码(部分):
public class org.levin.insidejvm.miscs.releasecode {
public static void main(java.lang.string[] args);
0 return
}
在上面的代码中,很明显debugcode和releasecode中的代码是一样的,只是appconfig.debug的值不一样而已,却产生了不同的中间代码,即编译器在appconfig.debug为false的时候直接忽略了if中的语句。利用这个特性,我们就可以根据配置来实现条件编译,从而实现不同的条件产生不同的中间代码而不只是不同的运行结果。
然而在这里为什么会出现这样的行为呢?
这是因为编译器对final修饰的基本类型和string类型的变量,在编译时解析为一个本地拷贝,这样拷贝导致编译器在编译的时候明确的知道releasecode的那一段if语句是不会被执行的,因而可以对其做优化。而这种替换的结果也使得用final修饰的int变量可以出现在switch-case语句中。
这种方式的缺陷
这种方式的缺陷在于要现实该机制的条件编译,在改变appconfig.debug中的值时,需要同时对appconfig类和releasecode类进行编译(即不能只编译appconfig类)。
参考:《深入java虚拟机(第二版)》第八章
2010-09-22
今天在java中字节码的格式的时候,发现method_info中的access_flags中竟然定了acc_bridge的值。网上搜了一下,大概理解它的意思了,先记之。
首先是在什么情况下会生成bridge方法(2):
bridge method may be created by the compiler when extending a parameterized type whose methods have parameterized arguments.
这是在网上找到的有人贴出来的一段话,但是感觉这段话说的并不是很明白。首先bridge方式是由编译器产生的,因而在源代码中也没有bridge的关键字。然后只有在以具体类型继承自一个泛型类,同时被继承的泛型类包含了泛型方法。比如看以下的例子:
他们生成的.class文件如下:
a.class
abstract class org.levin.insidejvm.miscs.bridgemethod.a {
public abstract java.lang.object method1(java.lang.object arg0);
public abstract java.lang.object method2();
}
b.class
class org.levin.insidejvm.miscs.bridgemethod.b extends org.levin.insidejvm.miscs.bridgemethod.a {
public java.lang.string method1(java.lang.string arg);
0 aload_1 [arg]
1 areturn
public java.lang.string method2();
0 ldc
2 areturn
public bridge synthetic java.lang.object method2();
0 aload_0 [this]
1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.b.method2() : java.lang.string [23]
4 areturn
public bridge synthetic java.lang.object method1(java.lang.object arg0);
0 aload_0 [this]
1 aload_1 [arg0]
2 checkcast java.lang.string [26]
5 invokevirtual org.levin.insidejvm.miscs.bridgemethod.b.method1(java.lang.string) : java.lang.string [28]
8 areturn
}
c.class
class org.levin.insidejvm.miscs.bridgemethod.c extends org.levin.insidejvm.miscs.bridgemethod.a {
public java.lang.object method1(java.lang.object arg);
0 aload_1 [arg]
1 areturn
public java.lang.object method2();
0 aconst_null
1 areturn
}
可以看到b中生成了两个bridge方法,而c中则没有。事实上,由于java中泛型有擦除的机制,因而在编译a类的时候,它里面定义的方法都是以object类型来表示了,因而如果没有bridge方法,b类根本没有覆盖a类中的abstract方法。正因为有bridge方法的存在,才使得b类可以编译通过。而c类由于在编译时所有的泛型也都是通过object类来表达的,因而它实现的也是a类中的abstract方法,因而不用再生成bridge方法了。
事实上b类中的bridge方法在调用也有一些区别:
这段方法的字节码如下:
0 new org.levin.insidejvm.miscs.bridgemethod.b [16]
3 dup
4 invokespecial org.levin.insidejvm.miscs.bridgemethod.b() [18]
7 astore_1 [b]
8 aload_1 [b]
9 ldc
11 invokevirtual org.levin.insidejvm.miscs.bridgemethod.b.method1(java.lang.string) : java.lang.string [21]
14 pop
15 new org.levin.insidejvm.miscs.bridgemethod.b [16]
18 dup
19 invokespecial org.levin.insidejvm.miscs.bridgemethod.b() [18]
22 astore_2 [a]
23 aload_2 [a]
24 ldc
26 invokevirtual org.levin.insidejvm.miscs.bridgemethod.a.method1(java.lang.object) : java.lang.object [25]
29 pop
30 return
以上的代码可以看出b变量调用的method1(string)的方法,而a变量调用的却是method1(object)方法。这种区别也正式因为bridge方法提供的支持才实现的。
事实上,bridge方法还会在另外一种情况下产生(2):
在java 1.4中,子类若要重写父类某个方法,那么子类的方法和父类的方法签名必须完全一致,包括方法名、参数类型以及返回值;而到java 1.5中,该机制变成,如果子类中某个方法的方法名和参数类型和父类某方法一致,并且子类该方法的返回值是父类相应方法返回值的类型或其子类型,那么该子类方法也可以重写父类中相应的方法。参看以下例子:
以上代码是可以编译通过的。让我们再来查看一下y的字节码:
class org.levin.insidejvm.miscs.bridgemethod.y extends org.levin.insidejvm.miscs.bridgemethod.x {
public org.levin.insidejvm.miscs.bridgemethod.f gete();
0 new org.levin.insidejvm.miscs.bridgemethod.f [16]
3 dup
4 invokespecial org.levin.insidejvm.miscs.bridgemethod.f() [18]
7 areturn
public bridge synthetic org.levin.insidejvm.miscs.bridgemethod.e gete();
0 aload_0 [this]
1 invokevirtual org.levin.insidejvm.miscs.bridgemethod.y.gete() : org.levin.insidejvm.miscs.bridgemethod.f [20]
4 areturn
}
从字节码上,我们可以看出语法本身事实上并没有发生变化,变化的只是编译器做的支持,它为重载方法重新生成了一个返回e而不是f的bridge方法。
从调用的字节码上可以更加明显的看出语法没有发生变化这一点:
字节码如下:
public static void main(java.lang.string[] args);
0 new org.levin.insidejvm.miscs.bridgemethod.y [16]
3 dup
4 invokespecial org.levin.insidejvm.miscs.bridgemethod.y() [18]
7 astore_1 [x]
8 aload_1 [x]
9 invokevirtual org.levin.insidejvm.miscs.bridgemethod.x.gete() : org.levin.insidejvm.miscs.bridgemethod.e [19]
12 pop
13 return
该字节码中x.gete()方法事实上调用的就是生成的bridge方法(e gete())方法,而不是用户定义的f gete()方法。
这种重载机制在某些,不同子类某个函数的返回值是不一样的,但是他们都需要重写父类中方法,以可以在某个点上通过父类实例统一调用。只是这种机制就需要返回值必须是继承于同一个类。事实上,这种方式在没有引入这种重写机制的时候也是可以实现的,只是现在java在编译器层面上提供了支持。
于2010年10月3日
注:这些文章都是前些时候写的,之前博客很乱,也都是随便贴一些自己写的或转载的,还有一些则是没有贴出来过的。现在打算好好整理一下,完整的记录自己的一些学习历程,而每次看到过去的时间,则让我想起以前的日子,因而我对时间一直是很重视的,所以每篇都著名写的日期,直到最先的文章出现。:)
java接口中的字段默认都是静态常量,不管在编码的时候有没有显示的指定static或者final。
如以下接口:
它编译生成的二进制代码如下:
public abstract interface org.levin.classfilereader.accessflag {
public static final int a = 10;
public static final int b = 20;
public static final int c = 30;
public static final int d = 40;
}
今天在看classloader源码的时候,突然发现里面有一个叫void的类使用,代码看起来挺新颖的,摘下来,以备后用。
public abstract class classloader {
private static void checkcreateclassloader() {
...
return null;
}
private classloader(void unused, classloader parent) {
this.parent = parent;
}
protected classloader(classloader parent) {
this(checkcreateclassloader(), parent);
}
protected classloader() {
this(checkcreateclassloader(), getsystemclassloader());
}
}
checkcreateclassloader()方法用返回void类型,该函数返回null,然后它就可以在其他函数的参数中调用了,只要该参数也是void类型的。
这种方式绕开了java中不能在函数参数中使用void类型的局限。
可以作为部分参考吧。呵呵。
于2010年9月15日