版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
摩根面试准备要点JVM架构(Vincent)ﻫ
重要涉及两个子系统和两个组件:Classloader(类装载器)子系统,Executionengine(执行引擎)子系统;Runtimedataarea(运营时数据区域)组件,Nativeinterface(本地接口)组件。ﻫ
Classloader子系统的作用:根据给定的全限定名类名(如java.lang.Object)来装载class文献的内容到Runtimedataarea中的methodarea(方法区域)。Javsa程序员可以extendsjava.lang.ClassLoader类来写自己的Classloader。ﻫ
Executionengine子系统的作用:执行classes中的指令。任何JVMspecification实现(JDK)的核心是Executionengine,换句话说:Sun的JDK和IBM的JDK好坏重要取决于他们各自实现的Execution
engine的好坏。每个运营中的线程都有一个Executionengine的实例。
Nativeinterface组件:与nativelibraries交互,是其它编程语言交互的接口。
ﻫ
Runtimedataarea组件:这个组件就是JVM中的内存。下面对这个部分进行具体介绍。Runtimedataarea的整体架构图Runtimedataarea重要涉及五个部分:Heap(堆),MethodArea(方法区域),JavaStack(java的栈),ProgramCounter(程序计数器),Nativemethodstack(本地方法栈)。Heap和MethodArea是被所有线程的共享使用的;而Javastack,Programcounter和Nativemethodstack是以线程为粒度的,每个线程独自拥有。ﻫ
Heap
Java程序在运营时创建的所有类实或数组都放在同一个堆中。而一个Java虚拟实例中只存在一个堆空间,因此所有线程都将共享这个堆。每一个java程序独占一个JVM实例,因而每个java程序都有它自己的堆空间,它们不会彼此干扰。但是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。(这里也许出现的异常java.lang.OutOfMemoryError:Javaheapspace)ﻫﻫMethodareaﻫ在Java虚拟机中,被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文献,然后读入这个class文献内容并把它传输到虚拟机中。紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。该类型中的类(静态)变量同样也存储在方法区中。与Heap同样,methodarea是多线程共享的,因此要考虑多线程访问的同步问题。比如,假设同时两个线程都企图访问一个名为Lava的类,而这个类还没有内装载入虚拟机,那么,这时应当只有一个线程去装载它,而另一个线程则只能等待。(这里也许出现的异常java.lang.OutOfMemoryError:PermGenfull)
Javastackﻫ
Javastack以帧为单位保存线程的运营状态。虚拟机只会直接对Javastack执行两种操作:以帧为单位的压栈或出栈。每当线程调用一个方法的时候,就对当前状态作为一个帧保存到javastack中(压栈);当一个方法调用返回时,从javastack弹出一个帧(出栈)。栈的大小是有一定的限制,这个也许出现StackOverFlow问题。下面的程序可以说明这个问题。publicclassTestStackOverFlow{ﻩpublicstaticvoidmain(String[]args){ﻩ Recursiver=newRecursive();ﻩﻩr.doit(10000);ﻩ //Exceptioninthread"main"java.lang.StackOverflowError }}classRecursive{ publicintdoit(intt){ﻩ if(t<=1){ ﻩﻩreturn1; }ﻩﻩreturnt+doit(t-1); }}
ﻫProgramcounterﻫ每个运营中的Java程序,每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址;,这里的地址可以是一个本地指针,也可以是在方法区中相相应于该方法起始指令的偏移量。
Nativemethodstackﻫ对于一个运营中的Java程序而言,它还能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运营时数据区,不止与此,它还可以做任何它想做的事情。比如,可以调用寄存器,或在操作系统中分派内存等。总之,本地方法具有和JVM相同的能力和权限。(这里出现JVM无法控制的内存溢出问题nativeheapOutOfMemory)
CLassLoader(Vincent)Java的可执行文献不同于C/C++,Java编译器只产生中间字节码文献(.class文献),由Java虚拟机(java.exe)解释执行。Java发布的程序(JAR包)也多半是一堆class文献,运营时由ClassLoader加载到Java虚拟机中执行。ClassLoader是Java虚拟机的重要组成部分,由Java语言编写,用户可以实现自定义的ClassLoader来完毕特定的功能。下面我们用例子说明ClassLoader。JVM规范定义了两种类型的ClassLoader:BootstrapClassLoader和User-definedClassLoader。JVM在运营时会产生三个ClassLoader:BootstrapClassLoader、ExtensionClassLoader和AppClassLoader。Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为BootstrapClassLoader。java中,什么叫不可更改的类(immutableclass)(KevinTam)从字面意思来理解就是不会发生变化的类,那么是什么不会发生变化呢,其实就是类的状态,也就是不变类的实例一旦被创建,其状态就不会发生变化,举个例子:假如人是一个class,那么我们中的每一个都是人这个类的具体的instance,假如人这个类只有一个状态就是生身父母,那么它就是一个不变类,由于每一个人在出生的那一刹那,生身父母就已经被设立了值,并且终生都不会发生变化。不变类有什么好处呢?1)不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。2)不变类的instance可以被reuse创建类的实例需要花费CPU的时间,当这个实例不再被引用时,将会被垃圾回收掉,这时候,又需要花费CPU的时间。对于不变类而言,一个好处就是可以将常用的实例进行缓存,从而减少了对象的创建。举个例子,对于布尔型,最常用的便是trueandfalse。JDK中的Boolean类就是一个不变类,并且对这两个实例进行了缓冲。publicfinalclassBooleanimplementsjava.io.Serializable{/***The<code>Boolean</code>objectcorrespondingtotheprimitive*value<code>true</code>.*/publicstaticfinalBooleanTRUE=newBoolean(true);/***The<code>Boolean</code>objectcorrespondingtotheprimitive*value<code>false</code>.*/publicstaticfinalBooleanFALSE=newBoolean(false);//这个方法不会创建新的对象,而是重用已经创建好的instancepublicstaticBooleanvalueOf(booleanb){return(b?TRUE:FALSE);}}3)不变类的某些方法可以缓存计算的结果hashCode这个方法来自于Object这个类,这个方法用来返回对象的hashCode,重要用于将对象放置到hashtable中时,来拟定这个对象的存储位置。对于一个不变类的实例,它的hashCode也是不变的,所以就可以缓存这个计算的结果,来提高性能,避免不必要的运算,JDK中的String类就是一个例子。publicfinalclassString{/**Cachethehashcodeforthestring*/privateinthash;//Defaultto0publicinthashCode(){inth=hash;if(h==0){//computethevaluehash=h;//cachethevalue}returnh;}}在JDK中,String,theprimitivewrapperclasses,andBigIntegerandBigDecimal都是不变类。假如一个类是不变类,这个类是不是就不能有改变状态的方法呢?答案当然是否认的,String是一个不变类,仍然有replace,replaceAll这样的方法,而String仍然是一个不变类,那是由于在这些改变状态的方法中,每次都是新创建一个String对象。假如大家理解了不变类,那也就不难理解为什么在做String的concatenate时,应当用StringBuffer而不是用+的操作符。如何对的使用String呢?1)不要用new去创建String对象。假如使用new去创建String,那么每次都会创建一个新对象。publicstaticvoidmain(String[]args){StringA1="A";StringA2="A";//Itwon'tcreateanewobjectcheckInstance(A1,A2);//Result:TheyaresameinstancesStringB1=newString("A");//createanewobjectStringB2=newString("A");//creatanewobjectcheckInstance(B1,B2);//Result:Theyaredifferentinstances}privatestaticvoidcheckInstance(Stringa1,Stringa2){if(a1==a2){System.out.println("Theyaresameinstances");}else{System.out.println("Theyaredifferentinstances");}}2)应当用StringBuffer来做连接操作由于String是一个不变类,那么在做连接操作时,就会创建临时对象来保存中间的运算结果,而StringBuffer是一个mutableclass,这样就不需要创建临时的对象来保存结果,从而提高了性能。JAVAGarbageCollection(Vincent)垃圾分代回收算法(GenerationalCollecting)
基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
ﻫﻫ如上图所示,为Java堆中的各代分布。ﻫ1.Young(年轻代)JVMspecification中的Heap的一部份
年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到此外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured);。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中也许同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。并且,Survivor区总有一个是空的。
2.Tenured(年老代)JVMspecification中的Heap的一部份
年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。ﻫ3.Perm(持久代)JVMspecification中的Methodareaﻫ用于存放静态文献,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用也许动态生成或者调用一些class,例如Hibernate等,在这种时候需要设立一个比较大的持久代空间来存放这些运营过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设立。ﻫ采用分区管理机制的JVM将JVM所管理的所有内存资源分为2个大的部分。永久存储区(PermanentSpace)和堆空间(TheHeapSpace)。其中堆空间又分为新生区(Young(New)generationspace)和养老区(Tenure(Old)generationspace),新生区又分为伊甸园(Edenspace),幸存者0区(Survivor0space)和幸存者1区(Survivor1space)。具体分区如下图:
ﻫ那JVM他的这些分区各有什么用途,请看下面的解说。
永久存储区(PermanentSpace):永久存储区是JVM的驻留内存,用于存放JDK自身所携带的Class,Interface的元数据,应用服务器允许必须的Class,Interface的元数据和Java程序运营时需要的Class和Interface的元数据。被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM时,释放此区域所控制的内存。ﻫ
堆空间(TheHeapSpace):是JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完毕。堆空间又分别按JAVA对象的创建和年龄特性分为养老区和新生区。ﻫﻫ新生区(Young(New)generationspace):新生区的作用涉及JAVA对象的创建和从JAVA对象中筛选出能进入养老区的JAVA对象。ﻫﻫ伊甸园(Edenspace):JAVA对空间中的所有对象在此出生,该区的名字因此而得名。也即是说当你的JAVA程序运营时,需要创建新的对象,JVM将在该区为你创建一个指定的对象供程序使用。创建对象的依据即是永久存储区中的元数据。ﻫ
幸存者0区(Survivor0space)和幸存者1区(Survivor1space):当伊甸园的空间用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁工作。同时将伊甸园中的尚有其他对象引用的对象移动到幸存者0区。幸存者0区就是用于存放伊甸园垃圾回收时所幸存下来的JAVA对象。当将伊甸园中的尚有其他对象引用的对象移动到幸存者0区时,假如幸存者0区也没有空间来存放这些对象时,JVM的垃圾回收器将对幸存者0区进行垃圾回收解决,将幸存者0区中不在有其他对象引用的JAVA对象进行销毁,将幸存者0区中尚有其他对象引用的对象移动到幸存者1区。幸存者1区的作用就是用于存放幸存者0区垃圾回收解决所幸存下来的JAVA对象。
养老区(Tenure(Old)generationspace):用于保存从新生区筛选出来的JAVA对象。
上面我们看了JVM的内存分区管理,现在我们来看JVM的垃圾回收工作是如何运作的。一方面当启动J2EE应用服务器时,JVM随之启动,并将JDK的类和接口,应用服务器运营时需要的类和接口以及J2EE应用的类和接口定义文献也及编译后的Class文献或JAR包中的Class文献装载到JVM的永久存储区。在伊甸园中创建JVM,应用服务器运营时必须的JAVA对象,创建J2EE应用启动时必须创建的JAVA对象;J2EE应用启动完毕,可对外提供服务。ﻫJVM在伊甸园区根据用户的每次请求创建相应的JAVA对象,当伊甸园的空间局限性以用来创建新JAVA对象的时候,JVM的垃圾回收器执行对伊甸园区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(假如该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到幸存者0区。
假如幸存者0区有足够控件存放则直接放到幸存者0区;假如幸存者0区没有足够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(假如该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到幸存者1区。ﻫ假如幸存者1区有足够控件存放则直接放到幸存者1区;假如幸存者0区没有足够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(假如该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到养老区。
假如养老区有足够控件存放则直接放到养老区;假如养老区没有足够空间存放,则JVM的垃圾回收器执行对养老区区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(假如该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并保存那些被其他对象所引用的JAVA对象。假如到最后养老区,幸存者1区,幸存者0区和伊甸园区都没有空间的话,则JVM会报告“JVM堆空间溢出(java.lang.OutOfMemoryError:Javaheapspace)”,也即是在堆空间没有空间来创建对象。ﻫ这就是JVM的内存分区管理,相比不分区来说;一般情况下,垃圾回收的速度要快很多;由于在没有必要的时候不用扫描整片内存而节省了大量时间。ﻫ通常大家还会碰到此外一种内存溢犯错误“永久存储区溢出(java.lang.OutOfMemoryError:JavaPermanentSpace)”。所有的垃圾收集算法都面临同一个问题,那就是找出应用程序不可到达的内存块,将其释放,这里面得不可到达重要是指应用程序已经没有内存块的引用了,而在JAVA中,某个对象相应用程序是可到达的是指:这个对象被根(根重要是指类的静态变量,或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用。Reference
Counting(引用计数)
引用计数是最简朴直接的一种方式,这种方式在每一个对象中增长一个引用的计数,这个计数代表当前程序有多少个引用引用了此对象,假如此对象的引用计数变为0,那么此对象就可以作为垃圾收集器的目的对象来收集。优点:简朴,直接,不需要暂停整个应用缺陷:1.需要编译器的配合,编译器要生成特殊的指令来进行引用计数的操作,比如每次将对象赋值给新的引用,或者者对象的引用超过了作用域等。2.不能解决循环引用的问题跟踪收集器跟踪收集器一方面要暂停整个应用程序,然后开始从根对象扫描整个堆,判断扫描的对象是否有对象引用,这里面有三个问题需要搞清楚:1.假如每次扫描整个堆,那么势必让GC的时间变长,从而影响了应用自身的执行。因此在JVM里面采用了分代收集,在新生代收集的时候minor
gc只需要扫描新生代,而不需要扫描老生代。2.JVM采用了分代收集以后,minor
gc只扫描新生代,但是minor
gc怎么判断是否有老生代的对象引用了新生代的对象,JVM采用了卡片标记的策略,卡片标记将老生代提成了一块一块的,划分以后的每一个块就叫做一个卡片,JVM采用卡表维护了每一个块的状态,当JAVA程序运营的时候,假如发现老生代对象引用或者释放了新生代对象的引用,那么就JVM就将卡表的状态设立为脏状态,这样每次minor
gc的时候就会只扫描被标记为脏状态的卡片,而不需要扫描整个堆。具体如下图:3.GC在收集一个对象的时候会判断是否有引用指向对象,在JAVA中的引用重要有四种:Strong
reference,Soft
reference,Weak
reference,Phantom
reference.Strong
Reference
强引用是JAVA中默认采用的一种方式,我们平时创建的引用都属于强引用。假如一个对象没有强引用,那么对象就会被回收。public
void
testStrongReference(){Object
referent
=
new
Object();Object
strongReference
=
referent;referent
=
null;System.gc();assertNotNull(strongReference);}
Soft
Reference软引用的对象在GC的时候不会被回收,只有当内存不够用的时候才会真正的回收,因此软引用适合缓存的场合,这样使得缓存中的对象可以尽量的再内存中待长期一点。Public
void
testSoftReference(){String
str
=
"test";SoftReference<String>
softreference
=
new
SoftReference<String>(str);str=null;System.gc();assertNotNull(softreference.get());}
Weak
reference弱引用有助于对象更快的被回收,假如一个对象没有强引用只有弱引用,那么在GC后,这个对象肯定会被回收。Public
void
testWeakReference(){String
str
=
"test";WeakReference<String>
weakReference
=
new
WeakReference<String>(str);str=null;System.gc();assertNull(weakReference.get());}
Phantom
reference
Mark-Sweep
Collector(标记-清除收集器)标记清除收集器最早由Lisp的发明人于1960年提出,标记清除收集器停止所有的工作,从根扫描每个活跃的对象,然后标记扫描过的对象,标记完毕以后,清除那些没有被标记的对象。优点:1
解决循环引用的问题2
不需要编译器的配合,从而就不执行额外的指令缺陷:1.每个活跃的对象都要进行扫描,收集暂停的时间比较长。Copying
Collector(复制收集器)复制收集器将内存分为两块同样大小空间,某一个时刻,只有一个空间处在活跃的状态,当活跃的空间满的时候,GC就会将活跃的对象复制到未使用的空间中去,本来不活跃的空间就变为了活跃的空间。复制收集器具体过程可以参考下图:优点:1
只扫描可以到达的对象,不需要扫描所有的对象,从而减少了应用暂停的时间缺陷:1.需要额外的空间消耗,某一个时刻,总是有一块内存处在未使用状态2.复制对象需要一定的开销Mark-Compact
Collector(标记-整理收集器)标记整理收集器汲取了标记清除和复制收集器的优点,它分两个阶段执行,在第一个阶段,一方面扫描所有活跃的对象,并标记所有活跃的对象,第二个阶段一方面清除未标记的对象,然后将活跃的的对象复制到堆得底部。标记整理收集器的过程示意图请参考下图:
Mark-compact策略极大的减少了内存碎片,并且不需要像Copy
Collector同样需要两倍的空间。JVM的垃圾收集策略
GC的执行时要花费一定的CPU资源和时间的,因此在JDK1.2以后,JVM引入了分代收集的策略,其中对新生代采用"Mark-Compact"策略,而对老生代采用了“Mark-Sweep"的策略。其中新生代的垃圾收集器命名为“minor
gc”,老生代的GC命名为"Full
Gc
或者Major
GC".其中用System.gc()强制执行的是Full
Gc.SpringIOCandAOP(Minjin)IoC和AOP都是Spring的核心思想ﻫﻫ
当然,最为一个框架级的轻量组件,大量的配置文献是不可缺少的,但是核心是要把这些配置文献,配置节组装起来,并将核心代码编写为完全业务无关的。我们看看Spring是怎么做的。
一方面,IoC,控制反转。Spring开发的基本思想:面向接口的编程模式。框架做的越多,应当越能发现接口在其中起到的作用,而Spring将这种想法,开始贯彻到业务的开发中了。Bean的Set方法使用接口作为参数,保证其扩展性,实现依赖关系的松偶尔。所谓的控制反转,作为中文更好理解的一个翻译应当是依赖注入,把依赖的类采用接口的方式,运用Set函数,传入Bean的内部,实现与外界的解耦合。这种注入也可作用于构造函数。
另一方面,AOP,面向切面的编程方式,我觉得更通俗的说法应当是对容器内的Bean进行方法干涉。被容器中创建的类,看起来执行一个普通的函数调用,由于被容器预解决,而会在方法执行前/后进行一些其他的、可配置的操作。当然,这种方法也同样是面向接口的,或者直接使用反射的。运用java.lang.reflect.InvocationHandler接口可以达成这种干涉的效果。下面是转载的一个简朴示例。Java代码import
java.lang.reflect.InvocationHandler;
import
java.lang.reflect.Method;
import
java.lang.reflect.Proxy;
public
class
DynaProxyHello
implements
InvocationHandler
{
private
Object
proxy;
private
Object
delegate;
public
Object
bind(Object
delegate,Object
proxy)
{
this.proxy
=
proxy;
this.delegate
=
delegate;
return
Proxy.newProxyInstance(
this.delegate.getClass().getClassLoader(),
this.delegate
.getClass().getInterfaces(),
this);
public
Object
invoke(Object
proxy,
Method
method,
Object[]
args)
throws
Throwable
{
Object
result
=
null;
try
{
//反射得到操作者的实例
Class
clazz
=
this.proxy.getClass();
//反射得到操作者的Start方法
Method
start
=
clazz.getDeclaredMethod("start",
new
Class[]
{
Method.class
});
//反射执行start方法
start.invoke(this.proxy,
new
Object[]
{
method
});
//执行要解决对象的原本方法
result
=
method.invoke(this.delegate,
args);
//
反射得到操作者的end方法
Method
end
=
clazz.getDeclaredMethod("end",
new
Class[]
{
Method.class
});
//
反射执行end方法
end.invoke(thixy,
new
Object[]
{
method
});
}
catch
(Exception
e)
{
e.printStackTrace();
}
return
result;
}
}
public
class
Test
{
public
static
void
main(String[]
args)
{
IHello
hello
=
(IHello)new
DynaProxyHello().bind(new
Hello(),new
LoggerOperation());
hello.sayGoogBye("Double
J");
hello.sayHello("Double
J");
}
}
ﻫﻫ
当然,不是只有这一个实现方式,java的代理功能只能代理接口,假如要代理类的化,可以使用cglib。ﻫ
Spring框架当然不会是上述的那么简朴(事实上它非常复杂),但是我关注的是核心的实现方式和设计思想。在有些时候,我们不需要使用Spring,甚至不能使用Spring(比如不用Java开发),但是这种思想和方式是可以复用的。使用这种设计思想,按照当前的语言和环境规定,实现自己的IoC和AOP框架。Spring框架(Minjin)Spring框架是一个分层架构,由7个定义良好的模块组成。Spring模块构建在核心容器之上,核心容器定义了创建、配置和管理bean的方式,如图1所示。
图1.Spring框架的7个模块
组成Spring框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:核心容器:核心容器提供Spring框架的基本功能。核心容器的重要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。Spring上下文:Spring上下文是一个配置文献,向Spring框架提供上下文信息。Spring上下文涉及公司服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。SpringAOP:通过配置管理特性,SpringAOP模块直接将面向方面的编程功能集成到了Spring框架中。所以,可以很容易地使Spring框架管理的任何对象支持AOP。SpringAOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用SpringAOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。SpringDAO:JDBCDAO抽象层提供了故意义的异常层次结构,可用该结构来管理异常解决和不同数据库供应商抛出的错误消息。异常层次结构简化了错误解决,并且极大地减少了需要编写的异常代码数量(例如打开和关闭连接)。SpringDAO的面向JDBC的异常遵从通用的DAO异常层次结构。SpringORM:Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中涉及JDO、Hibernate和iBatisSQLMap。所有这些都遵从Spring的通用事务和DAO异常层次结构。SpringWeb模块:Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以,Spring框架支持与JakartaStruts的集成。Web模块还简化了解决多部分请求以及将请求参数绑定到域对象的工作。SpringMVC框架:MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的,MVC容纳了大量视图技术,其中涉及JSP、Velocity、Tiles、iText和POI。Spring框架的功能可以用在任何J2EE服务器中,大多数功能也合用于不受管理的环境。Spring的核心要点是:支持不绑定到特定J2EE服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同J2EE环境(Web或EJB)、独立应用程序、测试环境之间重用。SpringAOP两种实现机制(BaomingChai)SPRING是通过动态代理来实现AOP的,SPRING内部提供了2种实现机制ﻫ1.假如是有接口声明的类进行AOP,spring调用的是java.lang.reflection.Proxy类来做解决org.springframework.aop.framework.JdkDynamicAopProxy publicObjectgetProxy(ClassLoaderclassLoader){ ﻩif(logger.isDebugEnabled()){ﻩﻩ ClasstargetClass=this.advised.getTargetSource().getTargetClass(); logger.debug("CreatingJDKdynamicproxy"+ ﻩﻩ(targetClass!=null?"for["+targetClass.getName()+"]":"")); ﻩ}ﻩ Class[]proxiedInterfaces=AopProxyUtpleteProxiedInterfaces(this.advised);ﻩﻩreturnProxy.newProxyInstance(classLoader,proxiedInterfaces,this);ﻩ}org.springframework.aop.framework.ReflectiveMethodInvocationpublicObjectproceed()throwsThrowable{ﻩ // Westartwithanindexof-1andincrementearly.ﻩ if(this.currentInterceptorIndex==this.interceptorsAndDynamicMethodMatchers.size()-1){ﻩ returninvokeJoinpoint();ﻩ } ObjectinterceptorOrInterceptionAdvice= erceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);ﻩﻩif(interceptorOrInterceptionAdviceinstanceofInterceptorAndDynamicMethodMatcher){ ﻩﻩ//Evaluatedynamicmethodmatcherhere:staticpartwillalreadyhave //beenevaluatedandfoundtomatch.ﻩﻩﻩInterceptorAndDynamicMethodMatcherdm= ﻩ(InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;ﻩﻩﻩif(dm.methodMatcher.matches(this.method,this.targetClass,this.arguments)){ ﻩ returndm.interceptor.invoke(this); ﻩ} ﻩﻩelse{ﻩ ﻩ//Dynamicmatchingfailed.ﻩ ﻩﻩ//Skipthisinterceptorandinvokethenextinthechain.ﻩﻩﻩﻩreturnproceed(); ﻩﻩ} }ﻩﻩelse{ ﻩ //It'saninterceptor,sowejustinvokeit:Thepointcutwillhaveﻩﻩ //beenevaluatedstaticallybeforethisobjectwasconstructed. ﻩreturn((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this); ﻩ} }2.假如是没有接口声明的类呢?SPRING通过CGLIB包和内部类来实现privatestaticclassStaticUnadvisedInterceptorimplementsMethodInterceptor,Serializable{ﻩﻩprivatefinalObjecttarget; ﻩpublicStaticUnadvisedInterceptor(Objecttarget){ﻩﻩﻩthis.target=target; ﻩ} ﻩpublicObjectintercept(Objectproxy,Methodmethod,Object[]args, MethodProxymethodProxy)throwsThrowable{ﻩ ObjectretVal=methodProxy.invoke(target,args);ﻩﻩﻩreturnmassageReturnTypeIfNecessary(proxy,target,retVal); } }ﻩ/** *Methodinterceptorusedforstatictargetswithnoadvicechain,whenthe *proxyistobeexposed. */ privatestaticclassStaticUnadvisedExposedInterceptorimplementsMethodInterceptor,Serializable{ ﻩprivatefinalObjecttarget;ﻩﻩpublicStaticUnadvisedExposedInterceptor(Objecttarget){ ﻩ this.target=target;ﻩﻩ} publicObjectintercept(Objectproxy,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable{ ﻩ ObjectoldProxy=null; try{ ﻩoldProxy=AopContext.setCurrentProxy(proxy);ﻩ ﻩObjectretVal=methodProxy.invoke(target,args); returnmassageReturnTypeIfNecessary(proxy,target,retVal);ﻩ ﻩ} ﻩ finally{ﻩﻩ ﻩAopContext.setCurrentProxy(oldProxy); ﻩ} }ﻩ}ﻩ/** *Interceptorusedtoinvokeadynamictargetwithoutcreatingamethodﻩ*invocationorevaluatinganadvicechain.(Weknowtherewasnoadviceﻩ*forthismethod.) */ privateclassDynamicUnadvisedInterceptorimplementsMethodInterceptor,Serializable{ﻩﻩpublicObjectintercept(Objectproxy,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable{ Objecttarget=advised.getTargetSource().getTarget();ﻩ ﻩtry{ ﻩ ObjectretVal=methodProxy.invoke(target,args); ﻩﻩ returnmassageReturnTypeIfNecessary(proxy,target,retVal); ﻩ} finally{ ﻩﻩadvised.getTargetSource().releaseTarget(target); ﻩ } }ﻩ} /**ﻩ*Interceptorforunadviseddynamictargetswhentheproxyneedsexposing.ﻩ*/ﻩprivateclassDynamicUnadvisedExposedInterceptorimplementsMethodInterceptor,Serializable{ ﻩpublicObjectintercept(Objectproxy,Methodmethod,Object[]args,MethodProxymethodProxy)throwsThrowable{ﻩﻩ ObjectoldProxy=null; Objecttarget=advised.getTargetSource().getTarget(); ﻩtry{ﻩ ﻩ oldProxy=AopContext.setCurrentProxy(proxy); ﻩ ﻩObjectretVal=methodProxy.invoke(target,args);ﻩ ﻩﻩreturnmassageReturnTypeIfNecessary(proxy,target,retVal); ﻩ} ﻩﻩfinally{ AopContext.setCurrentProxy(oldProxy); ﻩ advised.getTargetSource().releaseTarget(target); } } }我们自己也可以来试试ﻫ1.jdkproxy方式ﻫ
先来一个接口
IHelloWorld.javapackagekris.aop.test;publicinterfaceIHelloWorld{ﻩpublicvoidprint(Stringname); publicvoidwrite(Stringsth);}再来一个实现
ﻫHelloWorld.javapackagekris.aop.test;publicclassHelloWorldimplementsIHelloWorld{ﻩpublicvoidprint(Stringname){ ﻩSystem.out.println("HelloWorld"+name);ﻩ} publicvoidwrite(Stringsth){ﻩﻩSystem.out.println("write"+sth); ﻩ }}代理类
DefaultInvocationHandler.javapackagekris.aop.test;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;publicclassDefaultInvocationHandlerimplementsInvocationHandler{ /** *替换外部class调用的方法 *obj 外部已经已经包装好InvocationHandler的实例ﻩ*method 外部方法 *argsﻩﻩ方法参数ﻩ*/ publicObjectinvoke(Objectobj,Methodmethod,Object[]args)ﻩ throwsThrowable{ ﻩStrings1[]={"kris"};ﻩ Strings2[]={"anyone"}; IHelloWorldihw=newHelloWorld();ﻩ System.out.println("start!"); method.invoke(ihw,args);ﻩﻩmethod.invoke(ihw,s1);ﻩﻩObjecto=method.invoke(ihw,s2);ﻩﻩSystem.out.println("stop!");ﻩﻩreturno; }}测试类ﻫTest.javapackagekris.aop.test;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;publicclassTest{ﻩpublicstaticvoidmain(Stringargs[]){ﻩﻩClassclazz=newHelloWorld().getClass();ﻩ ClassLoadercl=clazz.getClassLoader();ﻩ Classclasses[]=clazz.getInterfaces(); InvocationHandlerih=newDefaultInvocationHandler(); //用InvocationHandler给HelloWorld进行AOP包装 ﻩIHelloWorldihw=(IHelloWorld)Proxy.newProxyInstance(cl,classes,ih);ﻩ ihw.print("test");ﻩﻩihw.write("test"); }}2.用CGLIB包实现,一方面不要忘了引入那个包packagekris.aop.cglib.test;publicclassHelloWorld{ﻩpublicvoidprint(Stringname){ﻩ System.out.println("HelloWorld"+name); }ﻩpublicvoidwrite(Stringsth){ﻩﻩSystem.out.println("write"+sth); }ﻩpublicvoidprint(){ﻩﻩSystem.out.println("HelloWorld"); }}代理类(没用内部类,看起来清楚点)packagekris.aop.cglib.test;importjava.lang.reflect.Method;importnet.sf.cglixy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;publicclassMethodInterceptorImplimplementsMethodInterceptor{ﻩpublicObjectintercept(Objectobj,Methodmethod,Object[]args, ﻩ MethodProxyproxy)throwsThrowable{ﻩ System.out.println(method);ﻩﻩproxy.invokeSuper(obj,args); returnnull; }}测试类packagekris.aop.cglib.test;importnet.sf.cglib.proxy.Enhancer;publicclassTest{ publicstaticvoidmain(String[]args){ﻩﻩEnhancerenhancer=newEnhancer(); enhancer.setSuperclass(HelloWorld.class); //设立回调方法实现类 enhancer.setCallback(newMethodInterceptorImpl());ﻩﻩ//实例化已经添加回调实现的HELLOWORLD实例 HelloWorldmy=(HelloWorld)enhancer.create(); my.print();ﻩ}}SpringAOP的底层实现技术(BaomingChai,StephenYu)AOP概述软件的编程语言最终的目的就是用更自然更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,我们看到编程语言在一步步用更自然、更强大的方式描述软件。AOP是软件开发思想的一个奔腾,AOP的引入将有效填补OOP的局限性,OOP和AOP分别从纵向和横向对软件进行抽象,有效地消除反复性的代码,使代码以更优雅的更有效的方式进行逻辑表达。AOP有三种植入切面的方法:其一是编译期织入,这规定使用特殊的Java编译器,AspectJ是其中的代表者;其二是类装载期织入,而这规定使用特殊的类装载器,AspectJ和AspectWerkz是其中的代表者;其三为动态代理织入,在运营期为目的类添加增强生成子类的方式,SpringAOP采用动态代理织入切面。SpringAOP使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理,之所以需要两种代理机制,很大限度上是由于JDK自身只提供基于接口的代理,不支持类的代理。基于JDK的代理和基于CGLib的代理是SpringAOP的核心实现技术,结识这两代理技术,有助于探究SpringAOP的实现机理。只要你乐意,你甚至可以抛开Spring,提供自己的AOP实现。带有横切逻辑的实例
ﻫ一方面,我们来看一个无法通过OOP进行抽象的反复代码逻辑,它们就是AOP改造的重要对象。下面,我们通过一个业务方法性能监视的实例了解横切逻辑。业务方法性能监视,在每一个业务方法调用之前开始监视,业务方法结束后结束监视并给出性能报告:代码清单2ForumService:包含性能监视横切代码packagecom.baobaotao.proxy;ﻫpublicclassForumServiceImplimplementsForumService...{
publicvoidremoveTopic(inttopicId)...{
//开始性能监视ﻫ
PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic");ﻫ
System.out.println("模拟删除Topic记录:"+topicId);ﻫ
try...{
Thread.currentThread().sleep(20);
}catch(Exceptione)...{ﻫ
thrownewRuntimeException(e);ﻫ
}ﻫ
//结束监视、并给出性能报告信息ﻫ
PerformanceMonitor.end();
}
publicvoidremoveForum(intforumId)...{
//开始性能监视ﻫPerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum");ﻫ
System.out.println("模拟删除Forum记录:"+forumId);ﻫ
try...{ﻫ
Thread.currentThread().sleep(40);ﻫ
}catch(Exceptione)...{ﻫ
thrownewRuntimeException(e);ﻫ
}ﻫ
//结束监视、并给出性能报告信息
PerformanceMonitor.end();ﻫ
}ﻫ}代码清单2中粗体表达的代码就是具有横切特性的代码,需要进行性能监视的每个业务方法的前后都需要添加类似的性能监视语句。ﻫ
我们保证实例的完整性,我们提供了一个非常简朴的性能监视实现类,如所示代码清单3所示:代码清单3PerformanceMonitorpackagecom.baobaotao.proxy;publicclassPerformanceMonitor{
//通过一个ThreadLocal保存线程相关的性能监视信息
privatestaticThreadLocal<MethodPerformace>performaceRecord=ﻫnewThreadLocal<MethodPerformace>();
publicstaticvoidbegin(Stringmethod){
System.out.println("beginmonitor...");
MethodPerformacemp=newMethodPerformace(method);
performaceRecord.set(mp);ﻫ
}
publicstaticvoidend(){
System.out.println("endmonitor...");
MethodPerformacemp=performaceRecord.get();ﻫ
mp.printPerformace();//打印出业务方法性能监视的信息
}
}PerformanceMonitor提供了两个方法,begin(Stringmethod)方法开始对某个业务类方法的监视,method为业务方法的署名,而end()方法结束对业务方法的监视,并给出性能监视的信息。由于每一个业务方法都必须单独记录性能监视数据,所以我们使用了ThreadLocal,ThreadLocal是削除非线程安全状态的不二法宝。ThreadLocal中的元素为方法性能记录对象MethodPerformace,它的代码如下所示:代码清单4MethodPerformacepackagecom.baobaotao.proxy;ﻫpublicclassMethodPerformace{ﻫ
privatelongbegin;
privatelongend;ﻫ
privateStringserviceMethod;ﻫ
publicMethodPerformace(StringserviceMethod){
this.serviceMethod=serviceMethod;
this.begin=System.currentTimeMillis();//记录方法调用开始时的系统时间ﻫ
}ﻫ
publicvoidprintPerformace(){ﻫ
//以下两行程序得到方法调用后的系统时间,并计算出方法执行花费时间
end=System.currentTimeMillis();ﻫ
longelapse=end-begin;
//报告业务方法执行时间ﻫ
System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
}ﻫ}通过下面代码测试这个拥有方法性能监视能力的业务方法:packagecom.baobaotao.proxy;
publicclassTestForumService{ﻫ
publicstaticvoidmain(String[]args){ﻫ
ForumServiceforumService=newForumServiceImpl();ﻫ
forumService.removeForum(10);
forumService.removeTopic(1012);
}
}我们得到以下的输出信息:beginmonitor...ﻫ模拟删除Forum记录:10
endmonitor...
com.baobaxy.ForumServiceImpl.removeForum花费47毫秒。beginmonitor...ﻫ模拟删除Topic记录:1012
endmonitor...ﻫcom.baobaotao.proxy.ForumServiceImpl.removeTopic花费16毫秒。如实例所示,要对业务类进行性能监视,就必须在每个业务类方法的前后两处添加上反复性的启动性能监视和结束性能监视的代码。这些非业务逻辑的性能监视代码破坏了作为业务类ForumServiceImpl的纯粹性。下面,我们分别JDK动态代理和CGLib动态代理技术,将业务方法中启动和结束性能监视的这些横切代码从业务类中完毕移除。JDK动态代理
ﻫ在JDK1.3以后提供了动态代理的技术,允许开发者在运营期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在我们终于发现动态代理是实现AOP的绝好底层技术。
JDK的动态代理重要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目的类的代码,动态将横切逻辑和业务逻辑编织在一起。
ﻫ而Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例。这样讲一定很抽象,我们立即着手动用Proxy和InvocationHandler这两个魔法戒对上一节中的性能监视代码进行AOP式的改造。
ﻫ一方面,我们从业务类ForumServiceImpl中删除性能监视的横切代码,使ForumServiceImpl只负责具体的业务逻辑,如所示:代码清单5ForumServiceImpl:移除性能监视横切代码packagecom.baobaotao.proxy;
publicclassForumServiceImplimplementsForumService{
publicvoidremoveTopic(inttopicId){
①
System.out.println("模拟删除Topic记录:"+topicId);ﻫ
try{
Thread.currentThread().sleep(20);ﻫ
}catch(Exceptione){ﻫ
thrownewRuntimeException(e);
}
②
}ﻫ
publicvoidremoveForum(intforumId){ﻫ
①
System.out.println("模拟删除Forum记录:"+forumId);ﻫ
try{ﻫ
Thread.currentThread().sleep(40);
}catch(Exceptione){
thrownewRuntimeException(e);ﻫ
}
②ﻫ
}ﻫ}在代码清单5中的①和②处,本来的性能监视代码被移除了,我们只保存了真正的业务逻辑。
ﻫ从业务类中移除的横切代码当然还得找到一个寄居之所,InvocationHandler就是横切代码的家园乐土,我们将性能监视的代码安顿在PerformaceHandler中,如代码清单6所示:代码清单6PerformaceHandlerpackagecom.baobaotao.proxy;
importjava.lang.reflect.InvocationHandler;ﻫimportjava.lang.reflect.Method;publicclassPerformaceHandlerimplementsInvocationHandler{
privateObjecttarget;ﻫ
publicPerformaceHandler(Objecttarget){//①target为目的的业务类ﻫ
this.target=target;
}ﻫ
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
throwsThrowable{ﻫ
PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName());ﻫ
Objectobj=method.invoke(target,args);//②通过反射方法调用目的业务类的业务方法ﻫ
PerformanceMonitor.end();ﻫ
returnobj;ﻫ
}ﻫ}粗体部分的代码为性能监视的横切代码,我们发现,横切代码只出现一次,而不是本来那样星洒各处。大家注意②处的method.invoke(),该语句通过反射的机制调用目的对象的方法,这样InvocationHandler的invoke(Objectproxy,Methodmethod,Object[]args)方法就将横切代码和目的业务类代码编织到一起了,所以我们可以将InvocationHandler当作是业务逻辑和横切逻辑的编织器。下面,我们对这段代码做进一步的说明。一方面,我们实现InvocationHandler接口,该接口定义了一个invoke(Objectproxy,Methodmethod,Object
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《淋巴细胞转化实验》课件
- 平面构成课件教学
- 帐沿市场需求与消费特点分析
- 包饺子机产品入市调查研究报告
- 医用羯布罗香油市场发展现状调查及供需格局分析预测报告
- 微控制器市场发展现状调查及供需格局分析预测报告
- 《珠宝品牌推广方案》课件
- 《突出的防治》课件
- 《可口可乐年会提案》课件
- 气动打钉枪市场发展现状调查及供需格局分析预测报告
- 大学生职业生涯规划成品
- 跟着音乐去旅行智慧树知到期末考试答案章节答案2024年浙江旅游职业学院
- (正式版)JBT 11270-2024 立体仓库组合式钢结构货架技术规范
- 汉语拼音字母表默写表
- 柴油机的振动与平衡-文档资料
- 2020高中化学选修三物质结构与性质书本知识归纳总结填空题附答案
- 音乐作品授权书(共3页)
- 广东省河流水功能二级区划成果表
- 酒驾私了协议书——范本
- 森林施工组织设计(完整版)
- 304不锈钢冷轧剥片缺陷分析及控制
评论
0/150
提交评论