Spring框架的控制反转(IoC)和面向切面编程(AOP)

每个应用程序都需要一些基础设施,如何利用现有的框架来提供这些基础设施服务呢?伴随着这个问题的提出,一个轻量级的J2EE(Java 2 Platform, Enterprise Edition)解决方案出现了,这就是Spring Framework。

Spring是为简化企业级系统开发而诞生的,Spring框架为J2EE应用常见的问题提供了简单、有效的解决方案,使用Spring,你可以用简单的POJO(Plain Old Java Object)来实现那些以前只有EJB才能实现的功能。这样不只是能简化服务器端开发,任何Java系统开发都能从Spring的简单、可测试和松耦合特征中受益。可以简单的说,Spring是一个轻量级的控制反转(IoC)和面向切面编程(AOP)容器框架Spring IoC,借助于依赖注入设计模式,使得开发者不用理会对象自身的生命周期及其关系,而且能够改善开发者对J2EE模式的使用;Spring AOP,借助于Spring实现的拦截器,开发者能够实现以声明的方式使用企业级服务,比如安全性服务、事务服务等。Spring IoC和 Spring AOP组合,一起形成了Spring这样一个有机整体,使得构建轻量级的J2EE架构成为可能,而且事实证明,非常有效。没有Spring IoC的Spring AOP是不完善的,没有Spring AOP的Spring IoC是不健壮的。本文阐述了Spring架构的控制反转和面向切面编程技术的原理,展示了Spring framework的简单、高效、可维护等优点。

注:本文大部分内容摘自《Spring.3.x企业应用开发实战》,涉及的例子也来自此书源代码。

1 Spring IoC

IoC(Inverse of Control, 控制反转),是Spring容器的内核,其他Spring的模块即在此基础上开花结果。

控制反转字面意思包括两点:

  •   控制:接口实现类的选择控制权;
  •   反转:选择控制权从调用类中移除,反转到第三方(容器或协作类)。

1.1 DI

由于IoC这个概念晦涩,Martin Fowler提出DI(Dependency Injection,依赖注入)来代替,即让调用类对某一接口实现类的依赖关系由第三方注入

Spring支持注入方式:构造函数注入和属性注入。

由此,Spring容器能帮助完成类的初始化与装配工作,让开发者从这些工作中脱离出来,专注于更有意义的业务逻辑开发工作。

1.2 相关Java基础

Java语言允许通过程序化的方式间接对Class进行操作。主要涉及的技术:

  •   类装载器,是一个重要的Java运行时系统组件,负责在运行时查找和装入class字节码文件,构造出类在JVM内部表示对象的组件;
  •   反射机制,通过反射可以从Class对象中获取Constructor、Method、Field等类元素的反射对象,我们就能以编程的方式通过这些反射对象对目标类对象进行操作。

图1.1中,类文件被装载并解析后,在JVM内将拥有一个对应的java.lang.Class类描述对象,该类的实例都有指向这个Class类描述对象的引用,而这个类描述对象又有指向关联ClassLoader的引用。

 

图1.1 类实例、类描述对象及类装载器的关系

 

每个类在JVM中都拥有一个对应的Class对象,它提供了类结构信息的描述,Class对象是在装载类时由JVM调用类装载器的defineClass()方法自动构造的。

上述类装载器和反射机制构成了IoC的强大基础:

(1)Spring获取到Class对象的构造器进行实例化Bean

请参见AbstractAutowireCapableBeanFactory # instantiateBean()方法,通过SimpleInstantiation

Strategy # instantiate()方法最终调用Constructor # newInstance()

(2)获取构造器或setter方法,注入依赖对象给该Bean

请参见AbstractAutowireCapableBeanFactory # applyPropertyValues()方法,通过BeanWrapperImpl # setPropertyValue()查找Bean中的注入类方法,最终调用Method # invoke()

如此Spring不耦合业务类代码,实现非入侵。

1.3 两大基础接口

Spring通过一个配置文件描述Bean和Bean间的依赖关系,利用反射功能实例化Bean并建立它们的依赖关系。完成这些底层工作主要通过两个基础接口:

(1)org.springframework.beans.factory.BeanFactory

  •   BeanFactory是Spring框架最核心的接口,提供高级IoC配置机制,管理不同类型的Java对象;
  •   BeanFactory一般称IoC容器,Spring框架基础设施,面向Spring本身。

(2)org.springframework.context.ApplicationContext

  •   ApplicationContext建立在BeanFactory基础上,提供更多面向应用的功能:
  •   ApplicationContext称应用上下文,也称Spring容器,面向使用Spring框架的开发者。

几乎所有的应用场合我们都直接使用ApplicationContext,而非底层的BeanFactory。

1.3.1 BeanFactory

BeanFactory是类的通用工厂,可以创建并管理各种类的对象,Spring称这些Java对象为Bean

Spring为BeanFactory提供了多种实现,最常用的是XmlBeanFactory,其类继承体系设计优雅,堪称经典,通过继承体系,我们很容易了解到XmlBeanFactory具有哪些功能,如图1.2所示。

 

图1.2 BeanFactory类继承体系

 

BeanFactory 接口位于类结构树的顶端,接口里面最主要的方法是getBean(String),方法的功能是返回特定名称的Bean。BeanFactory的强大功能是通过其它接口不断扩展,我们先对图1.2中的接口进行说明一下。

(1)AutowireCapableBeanFactory:定义了将容器中的Bean按某种规则(如按名字,类型的匹配等)进行自动装配的方法。值得注意的是ApplicationContext并没有实现此接口,在我们开发的应用中很少用到,更多的作用是和其它组件结合,把不在spring容器中的bean加入到spring的生命周期中。在ApplicationContext接口中可以通过getAutowireCapableBeanFactory()方法得到AutowireCapableBeanFactory对象。

(2)ListableBeanFactory:这个接口定义访问容器中的Bean基本信息:查看bean的个数,获取某一类型Bean的配置名,查看是否包含某一Bean;

(3)HierarchicalBeanFactory:父子级联IoC容器的接口,子容器可以通过接口方法访问父容器;ConfigurableBeanFactory接口就继承了此接口。如果使用了此接口,那么你也要相应的实现ConfigurableBeanFactory接口,因为ConfigurableBeanFactory中有设置父容器的方法,而HierarchicalBeanFactory中只有获取父容器的方法。

(4)ConfigurableBeanFactory:是一个重要的接口,增强了IoC容器的可定制性,它定义了设置类装载器ClassLoader、属性编辑器PropertyEditor、容器初始化后置处理器BeanPostProcessor等方法;几乎所有的BeanFactory类都会实现这个接口,赋予了BeanFactory可扩展的功能。

(5)ConfigurableListableBeanFactory:除了ListableBeanFactory、ConfigurableBean

Factory和AutowireCapableBeanFactory功能之外,还提供了访问和修改BeanDefinition(Spring配置文件中的每一个bean节点元素在spring容器里都通过一个BeanDefinition对象表示,描述了Bean的配置信息)及预实例化singleton列表的方法。

(6)BeanDefinitionRegistry: BeanDefinitionRegistry接口提供了向容器手工注册BeanDefinition对象的方法;

(7)SingletonBeanRegistry:定义了允许在运行期间向容器注册单实例Bean的方法;

举例:详见chapter3 –com.baobaotao.beanfactory.BeanFactoryTest.java

 

1.3.2 ApplicationContext

ApplicationContext由BeanFactory派生而来,提供更多面向实际应用的功能,使用BeanFactory则很多功能需要以编程的方式实现,而在ApplicationContext则可以通过配置的方式实现。

ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlAppl

icationContext,前者默认从类路径加载配置文件,后者默认从文件系统中装载配置文件,我们来了解一下ApplicationContext的类继承体系,如图1.3所示。

 

图1.3 ApplicationContext类继承体系

 

从图中我们可以看出ApplicationContext继承了HierarchicalBeanFactory和ListableBean

Factory接口,在此基础上,还通过多个其他的接口扩展了BeanFactory的功能,这些接口包括:

(1)ApplicationEventPublisher,让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了ApplicationListener事件监听接口的Bean可以接收到容器事件,并对事件进行响应处理。在ApplicationContext抽象实现类AbstractApplicationContext中,我们可以发现存在一个ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。

(2)MessageSource,为应用提供i18n国际化消息访问的功能;

(3)ResourcePatternResolver,所有ApplicationContext实现类都实现了类似于PathMatching

ResourcePatternResolver的功能,可以通过带前缀的Ant风格的资源文件路径装载Spring的配置文件。

(4)LifeCycle,该接口是Spring 2.0加入的,该接口提供了start()和stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被ApplicationContext及具体Bean实现,ApplicationContext会将start/stop的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX(Java Management Extension,系统、应用程序和资源管理的标准)、任务调度等目的。

ApplicationContext是Spring context核心接口,其由BeanFactory接口的子接口扩展而来,因此具有BeanFactory的所有功能,又因为其继承了MessageSource, ApplicationEventPublisher, ResourcePatternResolver接口,因此又具有国际化消息解析,资源加载,ApplicationEvent事件发布的功能。在此接口自己定义的方法中提供了获取父ApplicationContext、获取自动装配bean工厂(AutowireCapableBeanFactory)、获取启动时间和显示名称这四项功能。

ConfigurableApplicationContext扩展于ApplicationContext,它新增加了两个主要的方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下,调用refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。

接下来看继承结构中的实现类:

(1)AbstractApplicationContext类:对ApplicationContext进行抽象实现,其内部的refresh()方法实现了ApplicationContext的启动流程,其doClose()方法则实现了ApplicationCont

ext的关闭流程。另外对其继承的多个接口中的方法,均有默认实现。

此类给子类留下了三个抽象方法:

 

ConfigurableListableBeanFactory getBeanFactory();

void closeBeanFactory();

void refreshBeanFactory();

 

(2)AbstractRefreshableApplicationContext类:继承自AbstractApplicationContext类,支持多次刷新。此类对其基类AbstractApplicationContext所留下的三个方法均进行了默认实现,其中在refreshBeanFactory()方法中完成了刷新的功能,并调用了createBeanFactory()方法创建一个DefaultListableBeanFactory对象来作为代理的内部类,在其子类的实现中将通过此beanFactory来加载配置中的BeanDefinition,创建和管理bean

此类只留给子类一个抽象方法:

 

void loadBeanDefinitions(DefaultListableBeanFactory beanFactory);

 

从beanFactory中加载beanDefinitions。

(3)AbstractRefreshableConfigApplicationContext类:增加特殊配置路径的处理功能,作为基类服务于那些基于XML配置的应用上下文,比如ClassPathXmlApplicationContext、FileSystem

XmlApplicationContext、XmlWebApplicationContext等。

(4)AbstractXmlApplicationContext类:继承自AbstractRefreshableConfigApplication

Context类,其内部实现了从xml配置文件中加载和解析BeanDefinition的功能(即实现loadBeanDefinitions (DefaultListableBeanFactory beanFactory)方法)。

和BeanFactory初始化相似,ApplicationContext的初始化也很简单,如果配置文件放置在类路径下,用户可以优先使用ClassPathXmlApplicationContext实现类:

 

ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/

context/beans.xml");

 

对于ClassPathXmlApplicationContext来说,"com/baobaotao/context/beans.xml"等同于"classpath: com/baobaotao/context/beans.xml"。

如果配置文件放置在文件系统的路径下,则可以优先考虑使用FilySystemXmlApplication

Context实现类:

 

ApplicationContext ctx =  new FileSystemXmlApplicationContext("com/baobaotao/

context/beans.xml");

 

对于FileSystemXmlApplicationContext来说,“com/baobaotao/context/beans.xml”等同于“file:com/baobaotao/context/beans.xml”。 

还可以指定一组配置文件,Spring会自动将多个配置文件在内存中"整合"成一个配置文件,比如STARiBOSS系统启动客户端时,如下所示:

  1. public class LocalMain extends ApplicationObjectSupport {
  2.  private static final String PREFIX = "classpath*:com/star/sms/richclient/";
  3.  private static void launchClient() {
  4.       new MyApplicationLauncher(new String[] {
  5.               PREFIX + "application-context-core.xml",
  6.               PREFIX + "adapter-context-*.xml",
  7.               PREFIX + "client-context-*.xml",
  8.               PREFIX + "local-*.xml", });
  9.   ……

上述代码中MyApplicationLauncher(String[] rootContextPath)内部调用的正是

new ClassPathXmlApplicationContext(contextPaths)。

FileSystemXmlApplicationContext和ClassPathXmlApplicationContext都可以显式使用带资源类型前缀的路径,它们的区别在于如果不显式指定资源类型前缀,将分别把路径解析为文件系统路径和类路径。

在获取ApplicationContext实例后,就可以像BeanFactory一样调用getBean(beanName)返回Bean了。ApplicationContext的初始化和BeanFactory有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean;而ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean。因此ApplicationContext的初始化时间会比BeanFactory稍长一些,不过稍后的调用则没有"第一次惩罚"的问题。

举例:详见chapter3 –com.baobaotao.context.XmlApplicationContextTest.java

 

1.4 Bean的生命周期

Bean生命周期由多个特定的生命阶段组成,每个生命阶段都开出了一扇门,允许外界对Bean施加控制。在Spring中,我们可以从两个层面定义Bean的生命周期:第一个层面是Bean的作用范围;第二个层面是实例化Bean时所经历的一系列阶段。下面我们分别对BeanFactory和ApplicationCont

ext中Bean的生命周期进行分析。

1.4.1 BeanFactory中Bean的生命周期

由于Bean的生命周期所经历的阶段比较多,我们将通过一个图形化的方式进行描述,如图1.4所示。

 

图1.4 BeanFactory中Bean的生命周期完整过程

 

(1)当调用者通过getBean(beanName)向容器请求某一个Bean时,如果容器注册了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor接口,在实例化Bean之前,将调用接口的postProcessBeforeInstantiation()方法;

(2)根据配置情况调用Bean构造函数或工厂方法实例化Bean;

(3)如果容器注册了InstantiationAwareBeanPostProcessor接口,在实例化Bean之后,调用该接口的postProcessAfterInstantiation()方法,可在这里对已经实例化的对象进行一些"梳妆打扮";

(4)如果Bean配置了属性信息,容器在这一步着手将配置值设置到Bean对应的属性中,不过在设置每个属性之前将先调用InstantiationAwareBeanPostProcessor接口的postProcessProperty

Values()方法;

(5)调用Bean的属性设置方法设置属性值;

(6)如果Bean实现了org.springframework.beans.factory.BeanNameAware接口,将调用setBeanName()接口方法,将配置文件中该Bean对应的名称设置到Bean中;

(7)如果Bean实现了org.springframework.beans.factory.BeanFactoryAware接口,将调用setBeanFactory()接口方法,将BeanFactory容器实例设置到Bean中;

(8)如果BeanFactory装配了org.springframework.beans.factory.config.BeanPost

Processor后处理器,将调用BeanPostProcessor的Object postProcessBeforeInitialization

(Object bean, String beanName)接口方法对Bean进行加工操作。其中入参bean是当前正在处理的Bean,而beanName是当前Bean的配置名,返回的对象为加工处理后的Bean。用户可以使用该方法对某些Bean进行特殊的处理,甚至改变Bean的行为,BeanPostProcessor在Spring框架中占有重要的地位,为容器提供对Bean进行后续加工处理的切入点,Spring容器所提供的各种"神奇功能"(如AOP,动态代理等)都通过BeanPostProcessor实施;

(9)如果Bean实现了InitializingBean的接口,将调用接口的afterPropertiesSet()方法;

(10)如果在<bean>通过init-method属性定义了初始化方法,将执行这个方法;

(11)BeanPostProcessor后处理器定义了两个方法:其一是postProcessBeforeInitializat

ion()在第8步调用;其二是Object postProcessAfterInitialization(Object bean, String beanName)方法,这个方法在此时调用,容器再次获得对Bean进行加工处理的机会;

(12)如果在<bean>中指定Bean的作用范围为scope="prototype",将Bean返回给调用者,调用者负责Bean后续生命的管理,Spring不再管理这个Bean的生命周期。如果作用范围设置为scope="singleton",则将Bean放入到Spring IoC容器的缓存池中,并将Bean引用返回给调用者,Spring继续对这些Bean进行后续的生命管理;

(13)对于scope="singleton"的Bean,当容器关闭时,将触发Spring对Bean的后续生命周期的管理工作,首先如果Bean实现了DisposableBean接口,则将调用接口的destroy()方法,可以在此编写释放资源、记录日志等操作;

(14)对于scope="singleton"的Bean,如果通过<bean>的destroy-method属性指定了Bean的销毁方法,Spring将执行Bean的这个方法,完成Bean资源的释放等操作。

Bean的完整生命周期从Spring容器着手实例化Bean开始,直到最终销毁Bean,这当中经过了许多关键点,每个关键点都涉及特定的方法调用,可以将这些方法大致划分为三类:

(1)Bean自身的方法:如调用Bean构造函数实例化Bean,调用Setter设置Bean的属性值以及通过<bean>的init-method和destroy-method所指定的方法;

(2)Bean级生命周期接口方法:如BeanNameAware、BeanFactoryAware、InitializingBean 和DisposableBean,这些接口方法由Bean类直接实现;

(3)容器级生命周期接口方法:在图4中带 "★" 的步骤是由InstantiationAwareBeanPost

Processor和BeanPostProcessor这两个接口实现,一般称它们的实现类为"后处理器"。后处理器接口一般不由Bean本身实现,它们独立于Bean,实现类以容器附加装置的形式注册到Spring容器中并通过接口反射被Spring容器预先识别。当Spring容器创建任何Bean的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣Bean进行加工处理。

Bean级生命周期接口和容器级生命周期接口是个性和共性辩证统一思想的体现,前者解决Bean个性化处理的问题;而后者解决容器中某些Bean共性化处理的问题。

InstantiationAwareBeanPostProcessor其实是BeanPostProcessor接口的子接口,在Spring 1.2中定义,在Spring 2.0中为其提供了一个适配器类InstantiationAwareBeanPostProcessor

Adapter,一般情况下,可以方便地扩展该适配器覆盖感兴趣的方法以定义实现类。下面我们将通过一个具体的实例以更好地理解Bean生命周期的各个步骤。

我们依旧采用前面所介绍的Car类,让它实现所有Bean级的生命周期接口,此外还定义初始化和销毁的方法,这两个方法将通过<bean>的init-method和destroy-method属性指定。

举例:详见chapter3 –com.baobaotao.beanfactory.BeanLifeCycle.java

 

1.4.2 ApplicationContext中Bean的生命周期

Bean在ApplicationContext中的生命周期和在BeanFactory中生命周期类似,不同的是,如果Bean实现了org.springframework.context.ApplicationContextAware接口,会增加一个调用该接口方法setApplicationContext()的步骤,如图1.5所示.

图1.5 ApplicationContext中Bean的生命周期完整过程

 

此外,如果配置文件中声明了工厂后处理器接口BeanFactoryPostProcessor的实现类,则应用上下文在装载配置文件之后初始化Bean实例之前将调用这些BeanFactoryPostProcessor对配置信息进行加工处理。

ApplicationContext和BeanFactory另一个最大的不同之处在于:前者会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而后者需要在代码中通过手工调用addBeanPostProcessor()方法进行注册。这也是为什么在应用开发时,我们普遍使用ApplicationCon

Text,而很少使用BeanFactory的原因之一。

来看一个使用工厂后处理器的实例,假设我们希望对配置文件中car的brand配置属性进行调整,则可以编写一个如下的工厂后处理器:

  1. public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{    
  2.   //①car <bean>brand属性配置信息进行偷梁换柱的加工操作    
  3.   public void postProcessBeanFactory(ConfigurableListableBeanFactory bf)     
  4.  throws BeansException {    
  5.         BeanDefinition bd = bf.getBeanDefinition("car");    
  6.         bd.getPropertyValues().addPropertyValue("brand", "奇瑞QQ");    
  7.         System.out.println("调用BeanFactoryPostProcessor.postProcessBean Factory()");    
  8.     }    
  9. }        package com.baobaotao.context; 

    import org.springframework.beans.BeansException; 

    import org.springframework.beans.factory.config.BeanDefinition; 

    import org.springframework.beans.factory.config.BeanFactoryPost Processor; 

    import org.springframework.beans.factory.config.ConfigurableListable BeanFactory; 

    import com.baobaotao.Car; 

    

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{ 

      //①对car <bean>的brand属性配置信息进行“偷梁换柱”的加工操作 

      public void postProcessBeanFactory(ConfigurableListableBeanFactory bf)  

     throws BeansException { 

            BeanDefinition bd = bf.getBeanDefinition("car"); 

             

    bd.getPropertyValues().addPropertyValue("brand", "奇瑞QQ"); 

            System.out.println("调用BeanFactoryPostProcessor.postProcessBean Factory()!"); 

        } 

    } 

ApplicationContext在启动时,将首先为配置文件中每个<bean>生成一个BeanDefinition对象,BeanDefinition是<bean>在Spring容器中的内部表示。当配置文件中所有的<bean>都被解析成BeanDefinition时,ApplicationContext将调用工厂后处理器的方法,因此我们有机会通过程序的方式调整Bean的配置信息。在这里,我们将car对应的BeanDefinition进行调整,将brand属性设置为"奇瑞QQ",下面是具体的配置:

  1. <!--这个brand属性的值将被工厂后处理器更改掉-->   
  2.  <bean id="car" class="com.baobaotao.Car" init-method="myInit" destroy-method="myDestory"   
  3.        p:brand="红旗CA72"   
  4.        p:maxSpeed="200"/>   
  5.  <!--注册工厂后处理器-->   
  6. <bean id="myBeanFactoryPostProcessor"     
  7.        class="com.baobaotao.context.MyBeanFactoryPostProcessor"/>   
  8.  <!--注册Bean后处理器-->   
  9.  <bean id="myBeanPostProcessor"     
  10.        class="com.baobaotao.context.MyBeanPostProcessor"/>  

    <!--①这个brand属性的值将被工厂后处理器更改掉-->

     <bean id="car" class="com.baobaotao.Car" init-method="myInit" destroy-method="myDestory"

           p:brand="红旗CA72"

           p:maxSpeed="200"/>

     <!--②工厂后处理器-->

     <bean id="myBeanPostProcessor"  

           class="com.baobaotao.context.MyBeanPostProcessor"/>

     <!--③注册Bean后处理器-->

     <bean id="myBeanFactoryPostProcessor"  

           class="com.baobaotao.context.MyBeanFactoryPostProcessor"/>

②和③处定义的BeanFactoryPostProcessor和BeanPostProcessor会自动被Application

Context识别并注册到容器中。②处注册的工厂后处理器将会对①处配置的属性值进行调整。在③处,我们还声明了一个Bean后处理器,它也可以对Bean的属性进行调整。

举例:详见chapter3 –com.baobaotao.context.XmlApplicationContextTest

运行例子启动容器并查看car Bean的信息,将会看到car Bean的brand属性成功被工厂后处理器更改了。

Spring为Bean提供了细致周全的生命周期过程,通过实现特定的接口或通过<bean>属性设置,都可以对Bean的生命周期过程施加影响,Bean的生命周期不但和其实现的接口相关,还与Bean的作用范围有关。为了让Bean绑定在Spring框架上,我们推荐使用配置方式而非接口方式进行Bean生命周期的控制。

1.5 Spring高层视图

在使用Spring所提供的各项丰富而神奇的功能之前,我们必须在Spring IoC容器中装配好Bean,并建立Bean和Bean的关联关系。Spring的配置文件经过重大改进后变得简洁优雅,多种配置方式举例请参见chapter4源码。在Spring容器内部,这些不同配置方式的Bean定义信息是大体相同的。

Bean配置信息是Bean的元数据信息(实现类、属性信息、依赖关系、行为配置),在Spring容器中的内部对应物是一个个BeanDefinition形成的Bean定义注册表,图1.6描述了Spring容器、Bean配置信息、Bean实现类及应用程序的相互关系。

图1.6 Spring容器高层视图

 

Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载、实例化Bean,并建立Bean和Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序调用。

 

1.6 Spring容器内部工作原理

前面已经介绍了Spring容器的功用,接下来让我们看看其内部工作机制。

1.6.1 Spring容器启动逻辑

Spring的AbstractApplicationContext是ApplicationContext抽象实现类,该抽象类的refresh()方法定义了Spring容器在加载配置文件后的各项处理过程,这些处理过程清晰刻画了Spring容器启动时所执行的各项操作。下面,我们来看一下refresh()内部定义了哪些执行逻辑:

public abstract class AbstractApplicationContext extends DefaultResourceLoader

       implements ConfigurableApplicationContext, DisposableBean {

…… ……

public void refresh() throws BeansException, IllegalStateException {

       synchronized (this.startupShutdownMonitor) {

           // Prepare this context for refreshing.

           prepareRefresh();

           // Tell the subclass to refresh the internal bean factory.

           ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

           // Prepare the bean factory for use in this context.

           prepareBeanFactory(beanFactory);

           try {

              // Allows post-processing of the bean factory in context subclasses.

              postProcessBeanFactory(beanFactory);

              // Invoke factory processors registered as beans in the context.

              invokeBeanFactoryPostProcessors(beanFactory);

              // Register bean processors that intercept bean creation.

              registerBeanPostProcessors(beanFactory);

              // Initialize message source for this context.

              initMessageSource();

              // Initialize event multicaster for this context.

              initApplicationEventMulticaster();

              // Initialize other special beans in specific context subclasses.

              onRefresh();

              // Check for listener beans and register them.

              registerListeners();

              // Instantiate all remaining (non-lazy-init) singletons.

              finishBeanFactoryInitialization(beanFactory);

              // Last step: publish corresponding event.

              finishRefresh();

           }

           catch (BeansException ex) {

              // Destroy already created singletons to avoid dangling resources.

              destroyBeans();

              // Reset 'active' flag.

              cancelRefresh(ex);

              // Propagate exception to caller.

               throw ex;

           }

       }

 }

…… ……

refresh()方法的主要步骤为:

(1)初始化BeanFactory:根据配置文件实例化BeanFactory,getBeanFactory()方法由具体子类实现。在这一步里,Spring将配置文件的信息装入到容器的Bean定义注册表(BeanDefinitionReg

istry)中,但此时Bean还未初始化;

 (2)调用工厂后处理器:根据反射机制从BeanDefinitionRegistry中找出所有BeanFactory

PostProcessor类型的Bean,并调用其postProcessBeanFactory()接口方法;

(3)注册Bean后处理器:根据反射机制从BeanDefinitionRegistry中找出所有BeanPost

Processor类型的Bean,并将它们注册到容器Bean后处理器的注册表中;

 (4)初始化消息源:初始化容器的国际化信息资源;

 (5)初始化应用上下文事件广播器;

 (6)初始化其他特殊的Bean:这是一个钩子方法,子类可以借助这个钩子方法执行一些特殊的操作:如AbstractRefreshableWebApplicationContext就使用该钩子方法执行初始化ThemeSource的操作;

 (7)注册事件监听器;

 (8)初始化singleton的Bean:实例化所有singleton的Bean,并将它们放入Spring容器的缓存中;

 (9)发布上下文刷新事件:创建上下文刷新事件,事件广播器负责将些事件广播到每个注册的事件监听器中。

1.6.2 Bean的加工流水线

我们观摩了Bean从创建到销毁的生命历程,这些过程都可以在上面的过程中找到对应的步骤。Spring协调多个组件共同完成这个复杂的工程流程,图1.7描述了Spring容器从加载配置文件到创建出一个完整Bean的作业流程以及参与的角色。

 

图1.7 IoC内部工作流水线

 

(1)ResourceLoader从存储介质中加载Spring配置文件,并使用Resource表示这个配置文件的资源;

(2)BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;

(3)容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作

 1)对使用到占位符的<bean>元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;

 2)对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry);

(4)Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作;

(5)在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;

(6)利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。

 Spring容器确实堪称一部设计精密的机器,其内部拥有众多的组件和装置。Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:

 1)接口层描述了容器的重要组件及组件间的协作关系;

 2)继承体系逐步实现组件的各项功能。

 接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现,可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。

 Spring组件按其所承担的角色可以划分为两类:

 1)物料组件:Resource(资源)、BeanDefinition(<bean>元素标签中配置信息在容器中的内部表示形式)、PropertyEditor(将外部设置值转换为内部JavaBean属性值的编辑器)以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;

 2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy(采用实例化策略根据BeanDefinition对象创建Bean实例)以及BeanWrapper(完成Bean属性填充工作的代理器)、BeanPostProcessor等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。

 

1.7 容器事件

spring的ApplicationContext能够发布事件并且允许注册相应的事件监听器,它拥有一套完善的事件发布和监听机制。

Spring事件体系如图1.8所示,包括三个组件:

  •   事件:ApplicationEvent
  •   事件监听器:ApplicationListener,对监听到的事件进行处理。
  •   事件广播器:ApplicationEventMulticaster,将Spring发布的事件广播给所有的监听器。

 

图1.8 事件体系角色

 

Spring在ApplicationContext接口的抽象实现类AbstractApplicationContext中完成了事件体系的搭建。AbstractApplicationContext拥有一个applicationEventMulticaster成员变量,applicationEventMulticaster提供了容器监听器的注册表。AbstractApplicationContext在refresh()这个容器启动方法中通过以下三个步骤搭建了事件的基础设施。我们在前面代码中列出了refresh()内部的整个过程,为了阅读方便,在这里再次给出和事件体系有关的代码:

        // ⑤Initialize event multicaster for this context.

              initApplicationEventMulticaster();

              ……

              // ⑦Check for listener beans and register them.

              registerListeners();

              ……

          // ⑨Last step: publish corresponding event.

              finishRefresh();

整个事件体系结构如图1.9所示。

       图1.9 事件类、事件监听器、事件广播器继承及实现体系

 

举例:详见chapter5-src –com.baobaotao.event.ApplicatonEventTest.java

 

推荐资料:http://www.cnblogs.com/ITtangtang/p/3978349.html (Spring:源码解读Spring IOC原理)

 

2 Spring AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

Spring AOP是AOP技术在Spring中的具体体现,是构成Spring框架的另一个重要技术,Spring AOP构建于IoC之上,和IoC浑然天成统一于Spring的容器之中,也就是说,如果没有Spring IoC,Spring AOP是无法发挥他的强大的作用的,当然,如果没有Spring AOP,IoC还是有很多问题无法解决.

2.1 概述

2.1.2 AOP应用场合及主要意图

AOP的应用场合受限,适合于那些具有横切逻辑的应用场合,如性能监测、访问控制、事务管理、日志记录、安全控制、异常处理等。

虽然应用场合受限,但AOP是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.1.2 AOP概念

按照软件重构思想的理念,如果多个类中出现相同的代码,应该考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中,比如Horse、Pig、Camel这些对象都有run()、eat()的方法,通过引入一个包含这两个方法抽象的Animal父类,Horse、Pig、Camel就可以通过继承Animal复用到run()和eat()的方法。通过引入父类消除多个类中重复代码的方式在大多情况下是可行的,但世界并非永远这样简单,请看下面论坛管理业务类的代码:

 

上述代码中①的代码是方法性能监视逻辑,它在方法调用前启动,在方法调用返回前结束,并在内部记录性能监视的结果信息。而②的代码是事务开始和事务提交的代码。我们发现③处的业务代码淹没在重复化非业务性的代码之中,性能监视和事务管理这些非业务性代码葛藤缠树般包围着业务性代码。

 

 

如图2.1所示,假设我们将ForumService业务类看成一段圆木,将createForum()和removeTopic()方法分别看成圆木的一截,我们会发现性能监视和事务管理的代码就好像一个年轮,而业务代码是圆木的树心,这也正是横切代码概念的由来。

图2.1 横切逻辑的示意图

 

我们无法通过抽象父类的方式消除以上所示的重复性横切代码,因为这些横切逻辑依附在业务类方法的流程中,它们不能转移到其他地方去。

AOP独辟蹊径通过横向抽取机制为这类无法通过纵向继承体系进行抽象的重复性代码提供了解决方案。对于习惯了纵向抽取的开发者来说,可能不容易理解横向抽取方法的工作机制,因为Java语言本身不直接提供这种横向抽象的能力,我们暂把具体实现放在一旁,先通过图解的方式归纳出AOP的解决思路,如图2.2所示。

图2.2 横向抽取

 

可以看出AOP希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,还业务逻辑类一个清新的世界。

当然,我们知道将这些重复性的横切逻辑独立出来是很容易的,但如何将这些独立的逻辑融合到业务逻辑中完成和原来一样的业务操作,这才是事情的关键,也正是AOP要解决的主要问题。

2.1.3 AOP相关术语

AOP功能示意图如图2.3所示,即将Aspect(Advisor)中相应的Advice织入到Pointcut所指定目类的JoinPoint中。

图2.3 切面的逻辑实现(增强)通过切点织入到有多个连接点的目标对象

 

以下将一一介绍涉及到的相关术语:

(1)连接点(Joinpoint)

程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。我们知道黑客攻击系统需要找到突破口,没有突破口就无法进行攻击,从某种程度上来说,AOP是一个黑客(因为它要向目前类中嵌入额外的代码逻辑),连接点就是AOP向目标类打入楔子的候选点。

连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。Spring使用切点对执行点进行定位,而方位则在增强类型中定义

(2)切点(Pointcut)

切点是将要织入横切逻辑的方法的定位条件。每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。但在这为数众多的连接点中,如何定位到某个感兴趣的执行点上呢?AOP通过“切点”定位特定接连点。通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。其实确切地说,用切点定位应该是执行点而非连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息

(3)增强(Advice)

增强是织入到目标类连接点上的一段程序代码。是不是觉得AOP越来越像黑客了,这不是往业务类中装入木马吗?读者大可按照这一思路去理解增强,因为这样更形象易懂。在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点了!正因为增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以Spring所提供的增强接口都是带方位名的:BeforeAdvice、AfterRetuningAdvice、ThrowsAdvice等。BeforeAdvice表示方法调用前的位置,而AfterReturingAdvice表示访问返回后的位置。所以只有结合切点和增强两者一起上阵才能确定特定的连接点并实施增强逻辑

有很多书籍和文章将Advice译为“通知”,我们觉得“通知”的译法很不达意。我们可以知道通知者只是把某个消息传达给被通知者,并不会替被通知者做任何事情,而Spring的Advice必须嵌入到某个类的连接点上,并完成了一段附加的应用逻辑,这明显是去增强目标类的功能。当然,我们不能对这个翻译有过多的微词,毕竟Advice这个英文单词本身就有些不知所云,如果将其改为Enhancer,相信理解起来会更容易一些。

(4)目标对象(Target)

增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,就如ForumService中所示。在AOP的帮助下,ForumService只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。

(5)引介(Introduction)

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

(6)织入(Weaving)

织入是将增强添加到目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或者引介通过AOP这台织布机天衣无缝地编织到一起。我们不能不说“织入”这个词太精辟了。java程序执行可分三个时期,给我们提供三次织入机会。根据不同的实现技术,AOP有三种织入方式,分别对应这三个时期:

1)编译期织入,这要求使用特殊的Java编译器;

2)类装载期织入,这要求使用特殊的类装载器;

3)动态代理织入,在运行期为目标类添加增强生成代理类的方式。

Spring AOP采用动态代理织入(如图2.4所示),而AspectJ采用编译期织入和类装载期织入。

图2.4 Spring AOP运行期动态生成代理字节码示意图

 

(7)代理(Proxy)

一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

(8)切面(Aspect)

切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。(在Spring中,Advisor就是切面;但与通常的Aspect不同的是,Advisor通常只有一个Pointcut和一个Advice,而Aspect则可以包含多个Pointcut和多个Advice,因此Advisor是一种特殊的Aspect,但还是够用了。)

AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里首先包括两个工作:第一,如何通过切点和增强定位到连接点上;第二,如何在增强中编写切面的代码。Spring AOP大部分的内容都围绕这两点展开。它使用纯Java实现,不需要专门的编译过程,也不需要特殊的类装载器,在运行期通过代理方式向目标类织入增强代码。Spring并不尝试提供最完整的AOP实现,相反,它侧重于提供一种和Spring IoC容器整合的AOP实现,用以解决企业级开发中的常见问题。在Spring中,我们可以无缝地将Spring AOP、IoC和AspectJ整合在一起。

2.2 Spring AOP实现原理

Spring AOP的原理:使用动态代理技术在运行期织入增强逻辑,且内存中“临时”生成 AOP 动态代理类。

两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。

举例:详见chapter6 –com.baobaotao.proxy.TestForumService

在代码示例中,我们希望通过代理的方式,将业务类方法中开启和结束性能监视的这些横切代码从业务类中完全移除。并通过JDK动态代理技术或CGLib动态代理技术将横切代码动态织入到目标方法的相应位置。

2.2.1 JDK动态代理

JDK的动态代理要求目标类必须实现接口,如图2.5所示。

图2.5 JDK动态代理模式类图

 

JDK的动态代理,顾名思义,是用到JDK的反射机制,主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler,如图2.6所示。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。这样讲一定很抽象,我们马上着手使用Proxy和InvocationHandler这两个魔法戒对上一节中的性能监视代码进行革新。

图2.6 JDK动态代理底层实现

 

TestForumService示例中,JDK动态代理示例代码调用时序图如图2.7所示。

图2.7 JDK动态代理实例方法调用时序图

 

以下将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler),其中的getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。以下是生成并加载代理类的代码:

  1. public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces){
  2. … …
  3. //生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)
  4. proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);  
  5. //使用类加载器将字节码加载到内存中
  6. proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); 
  7. … …
  8. }
  9. public static byte[] generateProxyClass(String s, Class aclass[])
  10.     {
  11.         ProxyGenerator proxygenerator = new ProxyGenerator(s, aclass);
  12.         byte abyte0[] = proxygenerator.generateClassFile();
  13.         if(saveGeneratedFiles){
  14.            //保存到磁盘(将生成的字节码写入硬盘,默认情况下不保存到硬盘)
  15.               … …
  16.               Files.write(path, classFile, new OpenOption[0]);
  17.         … … }
  18. }

ProxyGenerator.generateClassFile()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。

  1. private byte[] generateClassFile(){
  2.    //添加接口中定义的方法,此时方法体为空  
  3.    for (int i = 0; i < this.interfaces.length; i++) {  
  4.      localObject1 = this.interfaces[i].getMethods();  
  5.      for (int k = 0; k < localObject1.length; k++) {  
  6.         addProxyMethod(localObject1[k], this.interfaces[i]);  
  7.      }  
  8.    }  
  9.    //添加一个带有InvocationHandler的构造方法  
  10.    MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);  
  11.    //循环生成方法体代码(省略)  
  12.    //方法体里生成调用InvocationHandlerinvoke方法代码。(此处有所省略)  
  13.    this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")  
  14. }

动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题:第一,代理类必须实现一个接口,如果没实现接口会抛出一个异常;第二,性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,经过测试大概每个代理类比静态代理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。

2.2.2 CGLib动态代理

对于没有通过接口定义业务方法的类,显然不能使用JDK动态代理,CGLib就能填补这个空缺。

CGLib(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

CGLib原理:运行期间字节码加载后动态生成一个要代理类的子类,子类重写被代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

CGLib底层:使用字节码处理框架ASM,来转换字节码并生成新的类,不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLib优点:对没有实现接口的类也能进行动态代理,性能比使用java反射的JDK动态代理约高10倍。

CGLib缺点:对于final方法,无法进行代理,且创建代理对象的时间约为JDK动态代理的8倍。

CGLib采用非常底层的字节码技术,可以为一个类创建子类,如图2.8所示。

图2.8 CGLib动态代理模式类图

 

CGLib用到了Enhancer类和MethodInterceptor接口,类之间的关系如图2.9所示。

图2.9 CGLib动态代理底层实现

 

TestForumService示例中,CGLib动态代理示例代码方法调用时序图如图2.10所示。

图2.10 CGLib动态代理实例方法调用时序图

 

使用CGLIB创建的代理对象,其实就是继承了要代理的目标类,然后对目标类中所有非final方法进行覆盖,但在覆盖方法时会添加一些拦截代码(MethodInterceptor中的intercept方法)。

其中,Enhancer.create()调用到AbstractClassGenerator.create(),其核心源码为:

  1. protected Object create(Object key) {
  2.    Class gen = null;
  3. … …
  4.     byte[] b = strategy.generate(this);//生成Class的二进制字节码, 放在byte数组中
  5.     String className = ClassNameReader.getClassName(new ClassReader(b));
  6.     getClassNameCache(loader).add(className);
  7.     //生成对应的Class实例,并放入缓存
  8.     gen = ReflectUtils.defineClass(className, b, loader);
  9. … …
  10.   return firstInstance(gen);
  11. }
  12. public class DefaultGeneratorStrategy implements GeneratorStrategy {
  13. … …
  14.     public byte[] generate(ClassGenerator cg) throws Exception {
  15.         ClassWriter cw = getClassWriter();
  16.         transform(cg).generateClass(cw);//ASM的封装
  17.         return transform(cw.toByteArray());
  18.     }
  19. }

2.2.3 动态代理小结

Spring AOP的底层就是通过使用JDK 动态代理或CGLib 动态代理技术为目标Bean织入横切逻辑。在这里,我们对动态创建代理对象作一个小结。

我们虽然通过PerformanceHandler或CglibProxy实现了性能监视横切逻辑的动态织入,但这种实现方式存在三个明显需要改进的地方:

(1)目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑

(2)需要通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;

(3)需要手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,?无法做到通用。

以上三个问题,在AOP 中占用重要的地位,因为Spring AOP 的主要工作就是围绕以上三点展开,Spring AOP 通过Pointcut(切点)指定在哪些类的哪些方法上施加横切逻辑。通过Advice(增强)描述横切逻辑和方法的具体织入点:方法前、方法后、方法的两端等。

此外,Spring 通过Advisor(切面)将Pointcut 和Advice 两者组装起来。有了Advisor的信息,Spring就可以利用JDK 或CGLib 的动态代理技术采用统一的方式为目标Bean 创建织入切面的代理对象了。

2.3 增强

Spring使用增强类定义横切逻辑,同时由于Spring 只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点的信息。

AOP 联盟为增强定义了org.aopalliance.aop.Advice 接口,Spring支持5 种类型的增强?

我们先来了解一下增强接口继承关系,如图2.11所示。

图2.11 增强接口继承关系图

 

带<<spring>>标识的接口是Spring 所定义的扩展增强接口,带<<aopalliance>>标识的接口则是AOP 联盟定义的接口。按照增强在目标类方法的连接点位置,可以分为以下5类:

(1)前置增强,org.springframework.aop.BeforeAdvice代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice 是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice 是为了将来版本扩展需要而定义的;

(2)后置增强,org.springframework.aop.AfterReturningAdvice代表后置增强,表示在目标方法执行后实施增强;

(3)环绕增强,org.aopalliance.intercept.MethodInterceptor,代表环绕增强,表示在目标方法执行前后实施增强;

(4)异常抛出增强,org.springframework.aop.ThrowsAdvic,代表抛出异常增强,表示在目标方法抛出异常后实施增强;

(5)引介增强,org.springframework.aop.IntroductionInterceptor,代表引介增强,表示在目标类中添加一些新的方法和属性。

这些增强接口都有一些方法,通过实现这些接口方法,在接口方法中定义横切逻辑,就可以将它们织入到目标类方法的相应连接点的位置。

举例:详见chapter6 - com.baobaotao.advice.TestBeforeAdvice(前置增强)

com.baobaotao.advice.TestThrowAdvice(异常抛出增强)

com.baobaotao.advice.TestMethodInterceptor(环绕增强)

 

2.4 环绕增强在STARiBOSS中的应用

2.4.1 缓存管理

部分示例如图2.12所示。

图2.12 STARiBOSS中AOP应用示例类继承体系

 

AbstractMethodCacheInterceptor用于缓存方法返回结果;ThreadCacheInterceptor用作线程级缓存;ThreadCacheStartInterceptor用于启动线程缓存,如果ThreadCacheInterceptor.isStart()为False,将通过ThreadCacheInterceptor.start()强制启动;ListMoreMethodInterceptor是我们平时能直观感受到的一个功能,用来判断查询出的结果是否大于指定条数,在拦截查询方法后,将查询结果中等于最大条数+1的数据过滤掉最后一条,同时界面出现如图2.13所示提示信息。

图2.13 STARiBOSS系统客户端查询结果数量超过上限提示信息

 

2.4.2 远程调用服务

配置如下:

    <bean id="baseRemoteProxy" abstract="true"

       class="com.star.sms.remote.http.HttpInvokerProxyFactoryBean">

       <property name="httpInvokerRequestExecutor" ref="httpInvokerRequestExecutor" />

       <property name="urlBasedHolder" ref="urlBasedHolder" />

        <property name="interceptors">

           <list>

              <ref bean="listMoreMethodInterceptor" />

              <ref bean="clientCacheInterceptor" />

              <ref bean="clientBasicCacheInterceptor" />

           </list>

       </property>

    </bean>

… …

    <!-- 业务受理 -->

    <bean id="client_acceptService" parent="baseRemoteProxy">

       <property name="businessInterface"

           value="com.star.sms.service.accept2.IAcceptService" />

    </bean>

其中,HttpInvokerProxyFactoryBean的父类HttpInvokerClientInterceptor实现MethodInterceptor接口。

2.5 切面

之前谈到的 AOP 框架其实可以将它理解为一个拦截器框架,但这个拦截器似乎非常武断。比如说,如果它拦截了一个类,那么它就拦截了这个类中所有的方法。类似地,当我们在使用动态代理的时候,其实也遇到了这个问题。需要在代码中对所拦截的方法名加以判断,才能过滤出我们需要拦截的方法,想想这种做法确实不太优雅。在大量的真实项目中,似乎我们只需要拦截特定的方法就行了,没必要拦截所有的方法。于是我们需要借助AOP 的一个很重要的工具——Advisor(切面),来解决这个问题。它也是 AOP 中的核心!是我们关注的重点!

也就是说,我们可以通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到 ProxyFactory 中,从而生成代理。这里提到这个“拦截匹配条件”在 AOP 中就叫做 Pointcut(切点),底层实现如图2.14所示。

图2.14 Pointcut 类关系图

 

Spring 通过org.springframework.aop.Pointcut 接口描述切点(Pointcut), 由ClassFilter 和MethodMatcher 构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。

由于增强既包含横切代码,又包含部分的连接点信息,所以我们可以仅通过增强类生成一个切面.但切点仅代表目标类连接点的部分信息,所以仅有切点,我们无法制作出一个切面,必须结合增强才能制作出切面.spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息.切面分为三类:一般切面,切点切面,引介切面:

  •   Advisor,代表一般切面,它仅包含一个Advice.我们说过,Advice包含了横切代码和连接点的信息,所以Advice本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛了,所以一般不会直接使用.
  •   PointcutAdvisor,代表具有切点的切面.它包含Advice和Pointcut两个类,这样,我们就可以通过类,方法名以及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面.
  •   IntroductionAdvisor,代表引介切面.引介切面是对应引介增强的特殊切面,它应用于类层面上,所以引介切点使用ClassFilter进行定义。

切面类体系如图2.15所示。

图2.15 切面实现类体系

 

PointcutAdvisor 主要有6 个具体的实现类,分别介绍如下:

(1)DefaultPointcutAdvisor,最常用的切面类型,它可以通过任意Pointcut和Advice 定义一个切面,唯一不支持的是引介的切面类型,一般可以通过扩展该类实现自定义的切面;

(2)NameMatchMethodPointcutAdvisor,通过该类可以定义按方法名定义切点的切面;

(3)RegexpMethodPointcutAdvisor,对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。RegexpMethodPointcutAdvisor允许用户以正则表达式模式串定义方法匹配的切点,其内部通过JdkRegexpMethodPointcut构造出正则表达式方法名切点。在Spring 2.1 版本中开始全力使用JDK 的正则表达式;

(4)StaticMethodMatcherPointcutAdvisor,静态方法匹配器切点定义的切面,默认情况下匹配所有的目标类;

(5)AspectJExpressionPointcutAdvisor,用于AspectJ切点表达式定义切点的切面,它是Spring 2.0 新提供的类;

(6)AspectJPointcutAdvisor,用于AspectJ 语法定义切点的切面,它也是Spring 2.0 新提供的类。

这些Advisor 的实现类,都可以在Pointcut 中找到对应物,实际上,它们都是通过扩展对应Pointcut实现类并实现PointcutAdvisor接口进行定义。如StaticMethodMatcherPointcutAdvisor扩展StaticMethodMatcherPointcut并实现PointcutAdvisor接口。此外,Advisor 都实现了org.springframework.core.Ordered接口,Spring 将根据Advisor定义的顺序决定织入切面的顺序。

举例:详见chapter6 - com.baobaotao.advisor.TestRegexpAdvisor(正则表达式方法名匹配切面) 及com.baobaotao.advisor.TestIntroduceAdvisor(引介切面)

2.6 Spring AOP 实现的基本线索

2.6.1 主要组件

上述内容及举例汇集后,得到Spring AOP主要组件如图2.16所示,其实现的基本线索围绕这个类继承体系展开。

图2.16 Spring AOP主要组件

然后,我们深入到Spring AOP核心代码的内部,看看代理对象的生成机制,拦截器横切逻辑以及织入的实现。

2.6.2 生成代理对象

我们选择上述示例中的ProxyFactoryBean作为入口点和分析的开始。ProxyFactoryBean是在Spring IoC环境中创建AOP应用的最底层方法,从中,可以看到一条实现AOP的基本线索。所有的逻辑从以下的方法开始,我们主要针对单例的代理对象的生成:

  1. public Object getObject() throws BeansException {
  2.       initializeAdvisorChain();//初始化增强器链
  3.       if (isSingleton()) {
  4.           return getSingletonInstance();
  5.       }
  6.       else {
  7. … …
  8.           return newPrototypeInstance();
  9.       }
  10.   }

对于getSingletonInstance()方法返回了什么,这就是代理对象如何产生的逻辑了,让我们追根溯源,看看传说中的proxy到底是如何一步一步的产生的。

  1.   private synchronized Object getSingletonInstance() {
  2.       if (this.singletonInstance == null) {
  3.           this.targetSource = freshTargetSource();
  4.           if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
  5.               // Rely on AOP infrastructure to tell us what interfaces to proxy.
  6.               Class targetClass = getTargetClass();
  7.             setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
  8.           }
  9.           // Initialize the shared singleton instance.
  10.           super.setFrozen(this.freezeProxy);
  11.           this.singletonInstance = getProxy(createAopProxy());
  12.       }
  13.       return this.singletonInstance;
  14.   }

ProxyFactoryBean是AdvisedSupport的子类,Spring使用AopProxy接口把AOP代理的实现与框架的其他部分分离开来。在AdvisedSupport中通过这样的方式来得到AopProxy,当然这里需要得到AopProxyFactory的帮助 ,DefaultAopProxyFactory是Spring用来生成AopProxy的地方,它包含JDK和Cglib两种实现方式,其createAopProxy()方法从JDK或者cglib中得到想要的代理对象:

  1. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  2.    // isOptimize()指代理对象是否采取进一步优化,为TRUE则采取CGLib生成代理
  3.    // isProxyTargetClass()指配置项中基于目标类还是接口,配置为true则采取CGLib生成代理
  4.      if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
  5.          Class targetClass = config.getTargetClass();
  6.          if (targetClass.isInterface()) {
  7.              return new JdkDynamicAopProxy(config);
  8.          }
  9.          return new Cglib2AopProxy(config);
  10.      }
  11.      else {
  12.          return new JdkDynamicAopProxy(config);
  13.      }
  14. }

可以看到其中的代理对象可以由JDK或者Cglib来生成,JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,上述getProxy()实现如下:

  1. protected Object getProxy(AopProxy aopProxy) {
  2.      return aopProxy.getProxy(this.proxyClassLoader);
  3. }

我们先进入JdkDynamicAopProxy实现中看看Proxy是怎样生成的:

  1. public Object getProxy(ClassLoader classLoader) {
  2.      Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
  3.      findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
  4.     //调用JDK动态代理生成代理实例
  5.      return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
  6. }

而Cglib2AopProxy中getProxy()为:

  1. public Object getProxy(ClassLoader classLoader) {
  2.              Enhancer enhancer = createEnhancer();
  3.       Object proxy;
  4.       //调用CGLib动态字节码生成技术创建代理实例
  5.          if (this.constructorArgs != null) {
  6.              proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
  7.          }
  8.          else {
  9.              proxy = enhancer.create();
  10.          }
  11.          return proxy;
  12. }

用Proxy包装target之后,通过ProxyFactoryBean得到对其方法的调用就被Proxy拦截了, ProxyFactoryBean的getObject()方法得到的实际上是一个Proxy了,target对象已经被封装了。对 ProxyFactoryBean这个工厂bean而言,其生产出来的对象是封装了目标对象的代理对象。

2.6.3 织入切面

下面的问题是,代理对象生成了,那切面是如何织入的?

我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。

  1. publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {
  2.        … …
  3.        try {
  4.          ……
  5.            //获得目标对象的类
  6.            target = targetSource.getTarget();
  7.            //获取可以应用到此方法上的Interceptor列表
  8.            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
  9.            //如果没有可以应用到此方法的增强(Interceptor),此直接反射调用 method.invoke(target, args)
  10.            if (chain.isEmpty()) {
  11.                 retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
  12.            } else {
  13.                 //创建MethodInvocation
  14.                 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  15.                 retVal = invocation.proceed();
  16.            }
  17.           … …
  18.            return retVal;
  19.        } finally {
  20.           … …
  21.        }
  22.     }

主流程可以简述为:获取可以应用到此方法上的增强链(Interceptor Chain),如果有,则应用增强,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是增强链是如何获取的以及它又是如何执行的,下面逐一分析下。

首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamic

InterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

  1. public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
  2.                    MethodCacheKeycacheKey = new MethodCacheKey(method);
  3.                    List<Object>cached = this.methodCache.get(cacheKey);
  4.                    if(cached == null) {
  5.                             cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
  6.                                                this,method, targetClass);
  7.                             this.methodCache.put(cacheKey,cached);
  8.                    }
  9.                    returncached;
  10.          }

可以看到实际的获取工作其实是由AdvisorChainFactory.getInterceptorsAndDynamic

InterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。

下面来分析下这个方法的实现:

  1. /**
  2.     * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
  3.     * 则判断此Advisor能否应用到目标类targetClass.如果是PointcutAdvisor,则判断
  4.     * Advisor能否应用到目标方法method.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
  5.     */
  6.     publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
  7.        // This is somewhat tricky... we have to process introductions first,
  8.        // but we need to preserve order in the ultimate list.
  9.        List interceptorList = new ArrayList(config.getAdvisors().length);
  10.        //查看是否包含IntroductionAdvisor
  11.        boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);
  12.        //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
  13.        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
  14.        Advisor[] advisors = config.getAdvisors();
  15.         for (int i = 0; i <advisors.length; i++) {
  16.            Advisor advisor = advisors[i];
  17.            if (advisor instanceof PointcutAdvisor) {
  18.                 // Add it conditionally.
  19.                 PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
  20.                 if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
  21.                     //TODO: 这个地方这两个方法的位置可以互换下
  22.                     //Advisor转化成Interceptor
  23.                     MethodInterceptor[]interceptors = registry.getInterceptors(advisor);
  24.                      //检查当前advisorpointcut是否可以匹配当前方法
  25.                     MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();
  26.                     if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
  27.                         if(mm.isRuntime()) {
  28.                             // Creating a newobject instance in the getInterceptors() method
  29.                             // isn't a problemas we normally cache created chains.
  30.                             for (intj = 0; j < interceptors.length; j++) {
  31.                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
  32.                             }
  33.                         } else {
  34.                             interceptorList.addAll(Arrays.asList(interceptors));
  35.                         }
  36.                     }
  37.                 }
  38.            } else if (advisor instanceof IntroductionAdvisor){
  39.                 IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
  40.                 if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
  41.                     Interceptor[] interceptors= registry.getInterceptors(advisor);
  42.                     interceptorList.addAll(Arrays.asList(interceptors));
  43.                 }
  44.            } else {
  45.                 Interceptor[] interceptors =registry.getInterceptors(advisor);
  46.                 interceptorList.addAll(Arrays.asList(interceptors));
  47.            }
  48.        }
  49.        return interceptorList;
  50. }

这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.

接下来我们再看下得到的拦截器链是怎么起作用的。

  1. if (chain.isEmpty()) {
  2.                 retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
  3.             } else {
  4.                 //创建MethodInvocation
  5.                 invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  6.                 retVal = invocation.proceed();
  7.             }

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建ReflectiveMethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码:

  1. public Object proceed() throws Throwable {
  2.        //  We start with an index of -1 and increment early.
  3.        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
  4.            //如果Interceptor执行完了,则执行joinPoint
  5.            return invokeJoinpoint();
  6.        }
  7.        Object interceptorOrInterceptionAdvice =
  8.            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
  9.        //如果要动态匹配joinPoint
  10.        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
  11.            // Evaluate dynamic method matcher here: static part will already have
  12.            // been evaluated and found to match.
  13.            InterceptorAndDynamicMethodMatcher dm =
  14.                 (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
  15.            //动态匹配:运行时参数是否满足匹配条件
  16.            if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
  17.                 //执行当前Intercetpor
  18.                 returndm.interceptor.invoke(this);
  19.            }
  20.            else {
  21.                 //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
  22.                 return proceed();
  23.            }
  24.        }
  25.        else {
  26.            // It's an interceptor, so we just invoke it: The pointcut will have
  27.            // been evaluated statically before this object was constructed.
  28.            //执行当前Intercetpor
  29.            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
  30.        }
  31. }

2.7 Spring AOP 事务增强

Spring 的声明式事务管理是通过Spring AOP实现的,通过事务的声明性信息.Spring负责将事务管理增强逻辑动态织入到相应连接点中。这些逻辑包括获取线程绑定资源、开始事务、提交/回滚事务、进行异常转换和处理等工作。这些事务管理代码完全从业务代码中移除,侵入性最小,非常符合非侵入式轻量级容器的理念。

STARiBOSS系统中事务管理通过很原始的TransactionProxyFactoryBean代理工厂类对需要事务管理的业务类进行代理,来实施事务功能的增强,配置方式如下:

  1. <bean id="abstractService" abstract="true"
  2.    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  3.    <property name="transactionManager" ref="transactionManager" />
  4.    <property name="transactionAttributes">
  5.        <props>
  6.            <prop key="*">PROPAGATION_REQUIRED,timeout_7200,-BossInnerServiceInvokeException,
  7.           -CallException</prop>
  8.        </props>
  9.    </property>
  10.   </bean>
  11. <bean id="transactionManager"
  12.       class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  13.       <property name="dataSource" ref="dataSource" />
  14.   </bean>
  15. <bean id="server_systemService" parent="abstractService">
  16.       <property name="target" ref="systemService" />
  17.   </bean>
  18. <bean id="systemService"  class="com.star.sms.business.system.SystemServiceImpl">
  19.       <property name="sysParameterDao" ref="sysParameterDao" />
  20.   </bean>

以上方式在Spring 3.0中已不被推荐,但有助于我们更直观地认识到Spring实施声明式事务的内在工作原理。TransactionProxyFactoryBean通过target属性指定需要代理的目标Bean,为业务Bean的不同方法配置事务属性,以上系统中每个服务类的每个方法均配置为PROPAGATION_REQUIRED传播行为,相同超时时间,发生BossInnerServiceInvokeException或CallException异常时均进行回滚。

TransactionProxyFactoryBean明显的缺点是需要单独配置每个需要事务支持的业务类,配置串规则麻烦,且业务类Bean不用到也要为代理类Bean而定义,造成相似的东西有两份配置,增加配置信息量。

这一切是因为早期Spring还没引入强大的AOP切面描述语言,Spring 2.0的一个重大改进是引入了AspectJ切面定义语言,解决事务方法切面描述的难题,且在基于Schema的配置中,添加了tx命名空间,在配置文件中以明确结构化的方式定义事务属性,大大提高配置事务属性的便利性。配合aop命名空间所提供的切面定义,业务类方法事务配置得到大大的简化,描述能力上也得到很大的提升。

举例:详见chapter9 - com.baobaotao.service.TestBbtForumTx

2.8 Spring LTW

在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入、类加载期织入和运行期织入。编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中;而类加载期织入则指通过特殊的类加载器,在类字节码加载到JVM时,织入切面;运行期织入则是采用CGLib工具或JDK动态代理进行切面的织入。

AspectJ提供了两种切面织入方式,第一种通过特殊编译器,在编译期,将AspectJ语言编写的切面类织入到Java类中,可以通过一个Ant或Maven任务来完成这个操作;第二种方式是类加载期织入,也简称为LTW(Load Time Weaving),Spring LTW原理如图2.17所示。

图2.17 Spring LTW 工作原理

 

Spring利用特定web容器的ClassLoader,通过LoadTimeWeaver将Spring提供的ClassFile

Transformer注册到ClassLoader中。在类加载期,注册的ClassFileTransformer读取AspectJ的配置文件,即类路径下的META-INF/aop.xml文件,获取切面,对加载到JVM中的Bean类进行字节码转换,织入切面。Spring容器初始化Bean实例时,采用的Bean类就是已经被织入了切面的类。

如何使用Load Time Weaving?首先,需要通过JVM的-javaagent参数设置LTW的织入器类包,以代理JVM默认的类加载器;第二,LTW织入器需要一个 aop.xml文件,在该文件中指定切面类和需要进行切面织入的目标类。

下面我将通过一个简单的例子在描述如何使用LTW。

举例:详见chapter7 - com.baobaotao.ltw.AspectjLtwTest

 

推荐资料:

http://blog.csdn.net/moreevan/article/details/11977115 (Spring AOP 实现原理)

http://blog.csdn.net/dreamthen/article/details/26687727 (Spring AOP 实现机制)

http://www.uml.org.cn/j2ee/201301102.asp (Spring AOP介绍及源码分析)

 

3 总结

本文主要围绕这两个概念:

(1)DI机制

依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,但在spring中创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者因此也称为依赖注入

(2)AOP

面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在Spring中主要表现为两个方面:

  1. 面向切面编程提供声明式系统级服务(良好隔离性和代码无关性)
  2. 支持用户自定义的切面

软件开发经历了从汇编语言到高级语言和从过程化编程到面向对象编程;前者是为了提高开发效率,而后者则使用了归纳法,把具有共性的东西进行归类并使之模块化,达到便于维护和扩展的目的;如果说面向对象编程可以对业务需求进行很好的分解使之模块化;那么面向切面编程AOP则可以对系统需求进行很好的模块组织,简化系统需求和实现之间的对比关系,是对OOP思想的一种补充;

简单总结,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架:

    1. 轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布,并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
    2. 控制反转——Spring通过控制反转(IoC)促进了松耦合。从而,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
    3. 面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑,仅此而已。它们并不负责(甚至是意识到)其它的系统级关注点,例如日志或事务支持。
    4. 容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个Bean如何被创建——基于一个可配置原型(prototype),你的Bean可以创建一个单态的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
    5. 框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了使用者。

所有Spring的这些特征使你能够编写更干净、更可管理且更易于测试的代码,它们也为Spring中的各种模块提供了基础支持。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值