首页 > 代码库 > Spring IOC 和 AOP

Spring IOC 和 AOP

Spring是一个轻量级的控制反转IoC)和面向切面(AOP)的容器框架。 

 1、控制反转(IOC)/依赖注入(DI):

        在传统的程序设计中,当调用者须要被调用者的协助时,通常由调用者来创建被调用者的实例。

但在spring里创建被调用者的工作不再由调用者来完毕。因此控制反转(IoC),为什么称为反转呢?反转是相对于正向而言的,那么什么算是正向的呢?考虑一下常规情况下的应用程序,假设要在A里面使用C,你会怎么做呢?当然是直接去创建C的对象,也就是说,是在A类中主动去获取所须要的外部资源C。这样的情况被称为正向的。

那么什么是反向呢?就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向的注入到A类中。
创建被调用者实例的工作通常由spring容器来完毕,然后注入调用者,因此也被称为依赖注入(DI),所以事实上依赖注入和控制反转是表达是同一个概念。仅仅是描写叙述的角度不同而已,依赖注入是从应用程序的角度在描写叙述。能够把依赖注入描写叙述完整点:应用程序依赖容器创建并注入它所须要的外部资源;而控制反转是从容器的角度在描写叙述。描写叙述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所须要的外部资源。


       Spring 攻克了一个很关键的问题他能够让你把对象之间的依赖关系转而用配置文件来管理,也就是他的依赖注入机制。而这个注入关系在一个叫 Ioc 容器中管理。那 Ioc 容器中就是被 Bean 包裹的对象。

Spring 正是通过把对象包装在 Bean 中而达到对这些对象管理以及一些列额外操作的目的。

        用图例来说明一下。先看没有IoC/DI的时候。常规的A类使用C类的示意图,如图1所看到的:

技术分享

图1  常规A使用C示意图

当有了IoC/DI的容器后,A类不再主动去创建C了。如图2所看到的:

技术分享

图2  A类不再主动创建C

而是被动等待。等待IoC/DI的容器获取一个C的实例,然后反向的注入到A类中,如图3所看到的:

技术分享 

图3  有IoC/DI容器后程序结构示意图

 
1.1、Spring容器

        Spring 通过一个配置文件描写叙述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。

Spring 的 IoC 容器在完毕这些底层工作的基 础上,还提供了 Bean 实例缓存、生命周期管理、Bean 实例代理、事件公布、资源装载等 高级服务。 Spring提供了多种容器的实现。分为两类。Bean工厂(由org.springframework.beans.factory.BeanFactory 接口定义)(最简单的容器,提供了基础的依赖注入支持) 和应用上下文(由org.springframework.context.ApplicationContext接口定义)(建立在Bean工厂基础之上,提供了系统架构服务。如从属性文件里读取文本信息,向相关的事件监听器公布事件)。

 1.1.1、Bean Factory

       Bean 工厂(com.springframework.beans.factory.BeanFactory)是 Spring 框架最核心的接 口,它提供了高级 IoC 的配置机制。 Bean工厂,不像其它工厂模式的实现,仅仅能分发一种类型的对象。Bean工厂能够创建和分发各种类型的Bean。

Bean工厂在实例化这些对象的时候还创建它们之间的关联关系(DI)。

Bean工厂还要參与Bean的生命周期中,调用用户定义好的初始化和销毁方法。

        Spring中有几种BeanFactory的实现方式。最经常使用的是org.springframework.beans.factory.xml.XmlBeanFactory.要创建XmlBeanFactory须要传递一个org.springframework.core.io.Resource实例给构造函数。


        BeanFactory  factory = new XmlBeanFactory(new FileSystemResource("c:/beans.xml"));
        MyBean myBean = (MyBean)factory.getBean("myBean");

       bean工厂延迟加载全部的单实例Bean。直到getBean()方法被调用的时候Bean才被创建。

 1.1.2、ApplicationContext

       建立在Bean工厂基础之上。提供了系统架构服务,如从属性文件里读取文本信息,向注冊为监听器的Bean发送事件。

ApplicationContext 的 主 要 实 现 类 是ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统 中装载配置文件, 除了提供的一些附加的功能外,与BeanFactory 还有一个重要的差别是关于单实例加载:bean工厂延迟加载全部的单实例Bean,直到getBean()方法被调用的时候Bean才被创建。上下文,会在上下文启动后预加载全部的单实例bean。

1.2、Bean的装配

1.2.1、使用XML Bean的装配

定义Car实现类

 

public class Car {
  private String color ;
  private Engine engine
  
  public Car() {}
 
  public Car(String color, Engine engine) {
    this.engine =  engine;
    this.color = color
  }
  
  public void drive() {
    System.out.println("hu hu hu …..");
  }
   public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Engine getEngine() {
        return engine;
    }
    public void setEngine(Engine engine) {
        this.engine = engine;
    }
}

构造函数注入

<bean id = "bmw" class = "class.test.Car">
     <constructor-arg value = http://www.mamicode.com/“Black”>//普通类型>

能够注入内部bean,參见以下的setter注入。
 

setter方法注入

<bean id = "audi" class = "class.test.Car"
     <property name = "color" value = http://www.mamicode.com/"White">//普通类型>

自己主动装配:

显式的装配会导致大量的XML,Spring自己主动装配,仅仅须要设置自己主动装配的<bean>中的autowire属性
<bean id = "kenny" class = "com.springinaciton.springidol.Instrumentalist" autowire = "autodetect">
 
Spring提供了四种自己主动装配类型:

  • byName:在容器中寻找和须要自己主动装配的属性名同样的Bean(或ID)。假设没有找到这个属性就没有装配上。

  • byType:在容器中寻找一个与自己主动装配的属性类型同样的Bean。假设没有找到这个属性就没有装配上。假设找到超过一个则抛出org.springframework.beans.factory.UnsatisfiedDependencyException异常。
  • constructor:在容器中查找与须要自己主动装配的Bean的构造函数參数一致的一个或多个Bean。假设存在不确定Bean或构造函数容器会抛出org.springframework.beans.factory.UnsatisfiedDependencyException异常。
  • autodetect:首先尝试使用constuctor来自己主动装配,然后使用byType方式。不确定性的处理与constructor方式和byType方式一样。都抛出org.springframework.beans.factory.UnsatisfiedDependencyException异常。

 

       Spring 默认情况下bean不会被自己主动装配的,但能够通过在spring配置文件的根元素<beans>中设置default autowire,就能够将全部的Bean设置为自己主动装配。

SpringBean自己主动装配的过程中非常easy出现不确定性,这些不确定性会导致程序无法执行。那么在我们的实际应用中要避免出现装配的不确定性。避免装配的不确定性的最好的方法就是使用显示装配和自己主动装配的混合方法。对有二义性的Bean使用显示装配,对没有二义性的Bean使用自己主动装配。
 
Bean范围化:
 
Spring默认全部的bean都是单一的。

但为使得每次都能产生一个新的Bean实例,能够声明Bean的scope属性为prototype。

这样当注入此bean时候,都会是不同的实例。
 
Spring范围化选项:

  • singleton 定义Bean的范围为每一个Spring容器一个实例(默认值)。

  • Prototype 同意Bean能够多次被实例化(使用一次创建一个实例)。
  • request  Bean的范围是HTTP请求。仅仅有在使用有web能力的spring上下文时才有效。
  • session  Bean的范围是HTTP会话。仅仅有在使用有web能力的spring上下文时才有效。
  • global-session Bean的范围是HTTP全局会话。仅仅有在portlet上下文才有效。


初始化和销毁Bean的时候运行一些动作。spring提供了两种方式
1、用init-method 和 destroy-method參数申明<bean>,假设在<beans>中申明这个两个參数,能够为上下文中全部的bean定义初始化和销毁时运行的方法。


2、类继承InitializingBean和DisposableBean接口

1.2.2、基于注解的Bean的装配

在一个稍大的项目中,假设组件採用xml的bean定义来配置,显然会添加配置文件的体积,查找以及维护起来也不太方便。

并且基于XML的Bean的装配,Bean的定义信息和Bean实现类是分离的。採用基于注解的配置方式时。Bean定义信息是通过在Bean实现类上标注解实现。

  • @Service用于标注业务层组件
  • @Controller用于标注控制层组件
  • @Repository用于标注数据訪问组件。即DAO组件

 

  • @Component泛指组件,当组件不好归类的时候,我们能够使用这个注解进行标注。 


 Spring2.5为我们引入了组件自己主动扫描机制。他在类路径下寻找标注了上述注解的类,并把这些类纳入进spring容器中管理。
 

applicationContext.xml:

<context:component-scan base-package="com.sankuai.meituan"/>

webmvc-config.xml:

<context:component-scan base-package="com.sankuai.meituan.**.web"/>

ps.

 

由于applicationContext是mvc context的父容器,mvc context能够引用applicationContext的bean。而applicationContext无法引用到mvc的bean,

 

spring查找bean,会如今当前context中查找,假设没有满足的,再到父容器查找,

 

applicationContext是在web.xml中配置的ContentLoader监听器启动的。当xml启动时载入,并依照一个约定的key放在java的ServletContext中。然后mvc 的servlet初始化时,先从ServletContext中依照约定的key取出来,以它为父容器,去创建mvc的容器。

@Service注解的类不能在springmvc.xml(相应crm中的webmvc-config.xml)中扫描,由于springmvc.xml与applicationContext.xml不是同一时候载入,假设不进行这种设置。那么,spring就会将全部带@Service注解的类都扫描到容器中,等到载入applicationContext.xml的时候,会由于容器已经存在Service类,使得cglib将不正确Service进行代理,直接导致的结果就是在applicationContext 中的事务配置不起作用,发生异常时,无法对数据进行回滚。



 自己主动装配:

 @Autowired
    @Autowired是Spring 提供的,需导入
    Package:org.springframework.beans.factory.annotation.Autowired;
    仅仅依照byType 注入。
 @Resource
    @Resource默认按 byName 自己主动注入,是J2EE提供的, 需导入Package:  
    javax.annotation.Resource;
    @Resource有两个中重要的属性:name和type 。而Spring将@Resource注解的name属性解析为bean的
    名字,而type属性则解析为bean的类型。所以假设使用name属性。则使用byName的自己主动注入策略。而使用
    type属性时则使用 byType自己主动注入策略。假设既不指定name也不指定type属性,这时将通过反射机制使用by
    Name自己主动注入策略。


 
    @Resource装配顺序 
  (1). 假设同一时候指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常;
  (2). 假设指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常;
  (3). 假设指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常;
  (4). 假设既没有指定name。又没有指定type,则自己主动依照byName方式进行装配;假设没有匹配。则回退为一个原始类型进行匹配,假设匹配则自己主动装配;
   

@Resource的作用相当于@Autowired,仅仅只是@Autowired按byType自己主动注入。


 
总结:基于XML方式的Bean的装配适合于Bean的实现类来源于第三方类库,如DataSource,JdbcTemplate等,因无法在类中标注注解。而基于注解的配置。较适合于当前项目中开发的Bean实现类。

2、面向切面编程(AOP):

       在面向对象编程(OOP)思想中。我们将事物纵向抽象成一个个的对象。而在面向切面编程中。我们将一个个对象某些类似的方面横向抽象成一个切面,对这个切面进行一些如权限验证,事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。Spring的一个关键的组件就是 AOP框架。 虽然如此。Spring IoC容器并不依赖于AOP,这意味着你能够自由选择是否使用AOP,AOP提供强大的中间件解决方式,这使得Spring IoC容器更加完好。

技术分享

技术分享


AOP几个术语:
通知(Advice):定义了切面,如Security,定义了切面的“什么”和“何时”
连接点(Joinpoint):     程序在运行过程中能够插入切面的点
切入点(Pointcut):通知要织如入的一个或多个连接点,定义了切面的“何地”
切面(Aspect):通知和切入点的结合
引入(Introduction):同意我们向现有的类加入新方法和属性。让现有的类具有新的行为和状态
目标(Target):被通知的对象。假设没有AOP。这个对象必须包括自己的主要逻辑和交叉业务的逻辑。


代理(Proxy):是向目标对象应用通知之后被创建的对象。对于client来说目标对象和代理对象是没有差别的。


织入(Weaving):把切面应用到目标对象来创建代理对象的过程。


三个重要的AOP框架
AspectJ(http://eclipse.org/aspectj)

 AspectJ是语言级的AOP实现,2001年由Xerox PARC的AOP小组公布,眼下版本号已经更新到1.6。AspectJ扩展了Java语言,定义了AOP语法。可以在编译期提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。主页位于http://www.eclipse.org/aspectj。

 
 

JBoss AOP (http://labs.jboss.com/portal/jobssaop/index.html)

 2004年作为JBoss应用程序server框架的扩展功能公布,能够从这个地址了解到JBoss AOP的很多其它信息:http://www.jboss.org/products/aop。 

Spring AOP (http://www.springframework.org )

Spring AOP使用纯Java实现,它不须要专门的编译过程,不须要特殊的类装载器,它在执行期通过代理方式向目标类织入增强代码。

Spring并不尝试提供最完整的AOP实现,相反,它側重于提供一种和Spring IoC容器整合的AOP实现,用以解决企业级开发中的常见问题。在Spring中,我们能够无缝地将Spring AOP、IoC和AspectJ整合在一起。

 


以下主要介绍Spring AOP,Spring对AOP的支持具有以下的4种情况:经典的基于代理的AOP(各版本号Spring),@AspectJ注解驱动的切面(仅Spring 2.0)。纯POJO切面(仅Spring 2.0),注入式的AspectJ切面(各版本号Spring)。

定义目标类

public interface Performer {
  public void perform() throws PerformanceException;
}
  
public class Singer implements Performer {
  private String name;
  private String song;
   
  public Singer(String name) {
    this(name, null);
  }
   
  public Singer(String name, String song) {
    this.name = name;
    this.song = song;
  }
   
  public void perform() throws PerformanceException {
    if(song == null) {
      throw new PerformanceException(name + " doesn't have a song to sing.");
    }
    System.out.println(name + " is singing " + song);
  }
   
  public void setSong(String song) {
    this.song = song;
  }

定义观众类

public class Audience {
  public Audience() {}
  public void watchPerformance(ProceedingJoinPoint jp) {
    System.out.println("The audience is taking their seats.");
    System.out.println("The audience is turning off their cellphones");
    try {
      jp.proceed();
      System.out.println("CLAP CLAP CLAP CLAP CLAP");
    } catch (Throwable throwable) {
      System.out.println("Boo! We want our money back!");    
    }
  }
  public void takeSeats() {
    System.out.println("The audience is taking their seats.");
  }
  public void turnOffCellPhones() {
    System.out.println("The audience is turning off their cellphones");
  }
  public void applaud() {
    System.out.println("CLAP CLAP CLAP CLAP CLAP");
  }
  public void demandRefund() {
    System.out.println("Boo! We want our money back!");
  }
}



2.1、经典的基于代理的AOP

创建通知
 

public class AudienceAdvice implements MethodBeforeAdvice,
    AfterReturningAdvice, ThrowsAdvice {
 
  public AudienceAdvice() {}
 
  //前通知,在方法之前调用
  public void before(Method method, Object[] args, Object target)
      throws Throwable {
    audience.takeSeats();
    audience.turnOffCellPhones();
  }
 
  //返回后通知。在成功返回后运行
  public void afterReturning(Object rtn, Method method,
      Object[] args, Object target) throws Throwable {
    audience.applaud();
  }
 
  //抛出后通知,在抛出异常后运行
  public void afterThrowing(Throwable throwable) {
    audience.demandRefund();
  }
 
  public void afterThrowing(Method method, Object[] args, Object target,
      Throwable throwable) {
    audience.demandRefund();
  }
 
  // injected
  private Audience audience;
  public void setAudience(Audience audience) {
    this.audience = audience;
  }
}


创建周围通知
相当于前通知,返回后通知,抛出后通知的结合,在spring里,周围通知是由MethodInterceptor接口定义的。周围通知的优点是能以简洁的方式在一个方法里定义全部的通知。

当然假设单独使用前通知或者后通知。周围通知的优势就减弱了。

后通知仅仅能够对被通知方法的返回值进行检查。不能改动。利用周围通知不仅能够检查。还能够改动返回值。让我们能够在把方法的返回值传递给调用者之前进行一些处理。
 

public class AudienceAroundAdvice implements MethodInterceptor {
  public Object invoke(MethodInvocation invocation) throws Throwable {
  
    try {
      //方法调用之前运行
      audience.takeSeats();
      audience.turnOffCellPhones();
  
      //调用目标方法
      Object returnValue = http://www.mamicode.com/invocation.proceed();>


定义切点与通知者
声明正則表達式切点

<bean id="performancePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="pattern" value=http://www.mamicode.com/".*perform" />>

把定义的切点与通知关联

<bean id="audienceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
     <property name="advice" value=http://www.mamicode.com/"audienceAdvice"/>>

 
联合切点与通知

<bean id="audienceAdvisor"
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  <property name="advice" ref="audienceAdvice" />
  <property name="pattern" value=http://www.mamicode.com/".*perform" />>

定义AspectJ切点
 

<bean id="audienceAdvisor"
     class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
   <property name="advice" ref="audienceAdvice" />
   <property name="expression" value=http://www.mamicode.com/"execution(* *.perform(..))" />>


创建代理

<bean id="singer"
    class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="target" ref="singerTarget" />
  <property name="proxyInterfaces"
      value=http://www.mamicode.com/"com.springinaction.springidol.Performer" />>

这里我们须要把实际的singer Bean 重命名为singerTarget,并且须要为全部代理的bean XML。


Spring自己主动代理被通知的Bean:

 
在spring AOP里,通知者把通知和切点关联起来。从而完整地定义了一个切面。

可是切面在spring里是以代理的方式实现的。好在Spring提供了BeanPostProcessor的一个方便实现:DefaultAdvisorAutoProxyCreator,它会自己主动检查通知者的切点是否匹配Bean的方法。而且使用通知的代理来替换这个Bean的定义。

它自己主动用匹配的通知者代理Bean。

为了使用DefaultAdvisorAutoProxyCreator。仅仅须要在配置例如以下的Bean:

创建自己主动代理

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

2.2、基于@AspectJ 注解驱动切面的自己主动代理

把Audience注解为一个切面
@Aspect
public class Audience {
    public Audience() {}
    @Pointcut("execution(* *.perform(..))")
    public void performance(){}
     
    @Before("performance()")
    public void takeSeats() {
        System.out.println("The audience is taking their seats.");
    }
    @Before("performance()")
    public void turnOffCellPhones() {
        System.out.println("The audience is turning off their cellphones");
    }
    @AfterReturning("performance()")
    public void applaud() {
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
    }
    @AfterThrowing("performance()")
    public void demandRefund() {
        System.out.println("Boo! We want our money back!");
    }
}

配置以下这个元素,这个元素会在spring上下文创建一个AnnotationAwareAspectJAutoProxyCreator,从而依据@Pointcut注解定义的切点来自己主动代理匹配Bean。

<aop:aspectj-autoproxy>

2.3、定义纯粹的POJO切面

<aop:config>
    <aop:pointcut
        id="performance"
        expression="execution(* *.perform(..))" />
 
    <aop:aspect ref="audience">
      <aop:around
          method="aroundAdvice"
          pointcut-ref="performance" />
      <aop:before
          method="takeSeats"
          pointcut-ref="performance" />
      <aop:before
          method="turnOffCellPhones"
          pointcut-ref="performance" />
      <aop:after-returning
          method="applaud"
          pointcut-ref="performance" />
      <aop:after-throwing
          method="demandRefund"
          pointcut-ref="performance" />
    </aop:aspect>
  </aop:config>

自己主动的使用JDK动态代理或者CGLIB来为目标对象创建代理。不要自己去定义自己的AutoProxyCreator
假设被代理的目标对象实现了至少一个接口。则会使用JDK动态代理。

全部该目标类型实现的接口都将被代理。

若该目标对象没有实现不论什么接口,则创建一个CGLIB代理。

假设你希望强制使用CGLIB代理 

<aop:config proxy-target-class="true">
...
</aop:config>

Spring IOC 和 AOP