Spring IOC与AOP

1.IOC原理

  IOC(Inversion of Control)控制反转。

  伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。

  IOC即借助于“第三方”(IOC容器)实现具有依赖关系的对象之间的解耦。

  IOC模式,系统中通过引入实现了IOC模式的IOC容器,即可由IOC容器来管理对象的生命周期、依赖关系等,从而使得应用程序的配置和依赖性规范与实际的应用程序代码分开。其中一个特点就是通过文本的配置文件进行应用程序组件间相互关系的配置,而不用重新修改并编译具体的代码。

  可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的,然后利用Java 的“反射”编程,根据XML中给出的类名生成相应的对象。从实现来看,IoC是把以前在工厂方法里写死的对象生成代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

  IoC中最基本的Java技术就是“反射”编程。反射又是一个生涩的名词,通俗的说反射就是根据给出的类名(字符串)来生成对象。这种编程方式可以让对象在生成时才决定要生成哪一种对象。反射的应用是很广泛的,象Hibernate、Spring中都是用“反射”做为最基本的技术手段。
  在过去,反射编程方式相对于正常的对象生成方式要慢10几倍,这也许也是当时为什么反射技术没有普通应用开来的原因。但经SUN改良优化后,反射方式生成对象和通常对象生成方式,速度已经相差不大了(但依然有一倍以上的差距)。

2.Spring IOC实现

  Resource定位过程:使用ClassPathResource来定位,寻找以文件形式存在的BeanDefinition信息。

  BeanDefinition的载入:相当于把定义的BeanDefinition在IOC容器中转化成一个Spring内部表示的数据结构的过程。

  BeanDefinition的解析:按照Spring的Bean定义规则来对这个XML的文档树进行解析。先调用XML的解析器DOM4J进行解析得到ducument对象,然后才按Spring规则进行解析。

  BeanDefinition的注册:为IOC容器提供了更友好的使用方式,是通过一个HashMap来持有载入的BeanDefinition的。在Map中可以更方便的进行检索和使用。

  依赖注入:

  *是否单件模式的Bean,是的话从缓存中取。

  *如果当前没有此Bean,就从双亲BeanFactory链一直向上查找。

  *获得当前Bean的所有依赖Bean,递归调用getBean。

  *工厂方法生成

  对象的生成其实是应用反射机制或CGLIB生出。

  1)生成对象

  *读取xml文件,获取beans标签下的所有bean标签。

  *利用Class.forName(class).newInstance(),我们就可以构造了一个bean的实例了。

  2)依赖对象

  其实依赖对象也是bean实例的一个属性,那么其实我们调用bean实例的setXXX()方法后,就可以为他注入依赖对象了,同样也是利用反射原理。

  *读取bean下面的所有 <property name="p" ref="persondao">标签

  *利用自省原理,获得bean的所有属性的集合,PropertyDescriptor[] ps=Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();

  *判断设置的 <property name="">标签中的name是否有在属性的集合中(ps里面),比如我们填写了标签 <property name="p"ref="persondao">,那么我们应该查找类中是否有Persondao p ;这个属性,如果没有的话,就不用设置p的值了。

  *接下去我们就可以调用属性的set方法,为bean实例设置值了。那么怎么获取set方法呢?我们可以调用PropertyDescriptor类的getWriteMethod方法,获取set方法,然后执行其invoke(要注入到的对象, 要、注入的值),如果没有set方法的话,我们自然也就不需要设置依赖对象的值了。

  autowire 

  开发人员都会拿DRY(讨厌重复劳动!Don't Repeat Yourself!)和KISS(保持简单,傻瓜!Keep It Simple, Stupid!)说笑。事实上,这正是优秀开发者必须具备的基本素质,即遵循这两条效率和敏捷原则。为简化DI容器中协作者的管理,Spring引入了Autowiring特性。

  方便之处在减少或者消除属性或构造器参数的设置,这样可以给我们的配置文件减减肥。

  其实,自动装配就是让我们少些几个  <ref ="...">.

  eg:当byName时,private Bean3 bean3;  这个bean3必须和<bean id="bean3" class="com.test.model.Bean3"

  parent="abstractBean"> 这个bean3相同.否则不能自动装配。

  CGLIB

  CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库(比java的反射或动态代理快很多!),它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO字节码的动态生成。
  代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。经常被用来动态地创建代理。JDK的动态代理用起来非常简单,但它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包。
  CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

  

3.AOP原理

  AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。

  可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态添加功能的一种技术。

  主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码

  在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

  面向对象编程主要用于为同一对象层次的公用行为建模。它的弱点是将公共行为应用于多个无关对象模型之间。而这恰恰是面向方面编程适合的地方。有了 AOP,我们可以定义交叉的关系,并将这些关系应用于跨模块的、彼此不同的对象模型。AOP 同时还可以让我们层次化功能性而不是嵌入功能性,从而使得代码有更好的可读性和易于维护。它会和面向对象编程合作得很好。

4.Spring AOP

  1)利用Spring AOP接口实现AOP,主要是为了指定自定义通知来供spring AOP机制识别。主要接口:前置通知 MethodBeforeAdvice ,后置通知:AfterReturningAdvice,环绕通知:MethodInterceptor,异常通知:ThrowsAdvice 。

  前置方法会在切入点方法之前执行,后置会在切入点方法执行之后执行,环绕会在切入点先执行around的前处理,然后执行切点方法,再执行return处理。最后执行around的后处理。

  spring 处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。所以是前通知依次执行,后通知到切入点执行完之后,从栈里在后进先出的形式把后通知执行。

  Spring AOP 框架对 AOP 代理类的处理原则是:如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类。

  JDK动态代理为什么必须使用接口:

  从创建代理函数看起,即public static Object newProxyInstance(ClassLoader loader,   Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

  通过源码可以看到,这个类第一步生成一个代理类(注意,这里的参数就是接口列表),Class cl = getProxyClass(loader, interfaces);

  然后通过代理类找到构造参数为InvocationHandler的构造函数并生成一个新类。

  Constructor cons = cl.getConstructor(constructorParams);//这个有用,在后面细说
  return (Object) cons.newInstance(new Object[] { h });  

  接口起什么作用呢,于是又看getProxyClass方法的代码,这个源码很长,就不细说了。大致分为三段:

    第一:验证

    第二:缓存创建新类的结构,如果创建过,则直接返回。(注意:这里的KEY就是接口列表)

    第三:如果没有创建过,则创建新类

  创建代码如下

      long num;
     //获得代理类数字标识 

     synchronized (nextUniqueNumberLock) {
        num = nextUniqueNumber++;
     }

      //获得创建新类的类名$Proxy,包名为接口包名,但需要注意的是,如果有两个接口而且不在同一个包下,也会报错

      String proxyName = proxyPkg + proxyClassNamePrefix + num;
      //调用class处理文件生成类的字节码,根据接口列表创建一个新类,这个类为代理类,
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces);
      //通过JNI接口,将Class字节码文件定义一个新类

      proxyClass = defineClass0(loader, proxyName,
         proxyClassFile, 0, proxyClassFile.length);

   根据前面的代码Constructor cons = cl.getConstructor(constructorParams);

   可以猜测到接口创建的新类proxyClassFile 不管采用什么接口,都是以下结构

    public class $Proxy1 extends Proxy implements 传入的接口{

    }
  生成新类的看不到源代码,不过猜测它的执行原理很有可能是如果类是Proxy的子类,则调用InvocationHandler进行方法的Invoke。

  所以,JDK动态代理的原理是根据定义好的规则,用传入的接口创建一个新类,这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。

 

参考:http://blog.csdn.net/m13666368773/article/details/7802126

http://javacrazyer.iteye.com/blog/794035

http://www.cnblogs.com/frankliiu-java/articles/1896443.html

百度百科

Spring技术内幕

posted on 2013-11-13 20:43  依蓝jslee  阅读(298)  评论(0编辑  收藏  举报

导航