Spring 学习笔记8---Spring AOP

前言

容器和AOP是Spring的两大核心。本文未来学习Spring AOP。html

AOP是什么?

AOP在计算机科学领域仍是相对年轻的概念,由Xerox PARC公司发明。Gregor Kiczales 在1997年领导一队研究人员首次介绍了AOP。当时他们关心的问题是如何在大型面向对象的代码库中重复使用那些必要且代价高的样板,那些样板的通用例子具备日志,缓存和事务功能。在最终的研究报告中,Kiczales和他的团队描述了OOP技术不能捕获和解决的问题,他们发现 横切关注点最终分散在整个代码中,这种交错的代码会变得愈来愈难开发和维护。他们分析了全部技术缘由,包括为什么这种纠缠模式会出现,为何避免起来这么困难,甚至涉及了设计模式的正确使用。该报告描述了一种解决方案做为OOP的补充,即便用“切面aspects”封装横切关注点以及容许重复使用。最终实现了 AspectJ,就是今天Java开发者仍然使用的一流AOP工具。

也就是说,AOP可不是Spring发明的,Spring只是对AOP作了支持而已。既然如此,AOP里面的几个概念就是通用的了。java

《Spring in Action》这本书给出了明确的解释:spring

在软件开发中,散布于应用中多处的功能被称为横切关注点(cross-cutting concern)。一般来说,这些横切关注点从概念上是与应用的业务逻辑相分离的。好比:日志、声明式事物、安全和缓存。这些东西都不是咱们平时写代码的核心功能,但许多地方都要用到。
把这些横切关注点与业务相分离正是面向切面编程(AOP)索要解决的问题。
简单的说就是把这些许多地方都要用到,但又不是核心业务的功能,单独剥离出来封装,经过配置指定要切入到指定的方法中去。sql

 

如上图所示,这就是横切关注点的概念,水平的是核心业务,这些切入的箭头就是咱们的横切关注点。express

横切关注点能够被模块化为特殊的类,这些类被称为切面(aspect)。这样作有两个好处:编程

首先,如今每一个关注点都集中于一个地方,而不是分割到多处代码中
其次,服务模块更简洁,由于它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。设计模式

AOP术语

通知(Advice):
在AOP中,切面的工做被称为通知。通知定义了切面“是什么”以及“什么时候”使用。除了描述切面要完成的工做,通知还解决了什么时候执行这个工做的问题。
Spring切面能够应用5种类型的通知:api

  • 前置通知(Before):在目标方法被调用以前调用通知功能
  • 后置通知(After):在目标方法完成以后调用通知,此时不会关心方法的输出是什么
  • 返回通知(After-returning):在目标方法成功执行以后调用通知
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用以前和调用以后执行自定义的行为

链接点(Join point):
链接点是在应用执行过程当中可以插入切面的一个点。这个点能够是调用方法时、抛出异常时、甚至修改一个字段时。切面代码能够利用这些点插入到应用的正常流程之中,并添加行为。缓存

切点(Pointcut):
若是说通知定义了切面“是什么”和“什么时候”的话,那么切点就定义了“何处”。好比我想把日志引入到某个具体的方法中,这个方法就是所谓的切点。安全

切面(Aspect):
切面是通知和切点的结合。通知和切点共同定义了切面的所有内容———他是什么,在什么时候和何处完成其功能。

引入(Introduction):
引入容许咱们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。

织入(Weaving):
把切面应用到目标对象来建立新的代理对象的过程,织入通常发生在以下几个时机:

  • 编译时:当一个类文件被编译时进行织入,这须要特殊的编译器才能够作的到,例如AspectJ的织入编译器。
  • 类加载时:使用特殊的 ClassLoader 在目标类被加载到程序以前加强类的字节代码。
  • 运行时:切面在运行的某个时刻被织入, 方式是容器为目标对象动态地建立一个代理对象。Spring AOP就是以这种方式织入切面的。

Spring对AOP的支持

同依赖注入同样,Spring AOP也提供两种配置:

  • 基于Java Annotation;
  • 基于Xml配置;

Spring AOP是基于动态代理实现的,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。至于什么是动态代理,后文将会解释。
动态代理只能处理方法,所以Spring AOP只支持方法链接点。可是一般方法拦截能够知足大部分需求。若是须要字段或构造器拦截,别忘了Spring是一个高度可扩展的框架,可使用AspectJ 等第三方AOP框架。

下面就举两个例子分别来介绍下Spring AOP的两种配置方式,咱们就拿简单的日志来讲明。

使用Java注解配置

@Component //声明bean
@Aspect //该注解标示该类为切面类
public class LogAspect {
    @Pointcut("execution(* com.springdemo.service.impl.UserServiceImpl.*(..))")
    public void logAop(){}

    @Before("logAop() && args(name)")
    public void logBefore(String name){
        System.out.println(name+"前置通知Before");
    }

    @AfterReturning("logAop()")
    public void logAfterReturning(){
        System.out.println("返回通知AfterReturning");
    }

    @After("logAop() && args(name)")
    public void logAfter(String name){
        System.out.println(name+"后置通知After");
    }

    @AfterThrowing("logAop()")
    public void logAfterThrow(){
        System.out.println("异常通知AfterThrowing");
    }
}

  

该代码片断就声明了一个bean,而这个bean是一个切面,其中的方法将会应用于com.springdemo.service.impl.UserServiceImpl类的任何方法。

UserServiceImpl类很简单,只是个普通的bean:

package com.springdemo.service.impl;
@Service("userService")
public class UserServiceImpl implements UserService{
    @Override
    public void sayHello(String name) {
        System.out.println("hello, "+name);
    }
}

  

最后,还要启用Spring AOP的注解配置。一样采用Java配置:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class Config{
...
}

  

注意不要被注解里的“AspectJ”字样吓到了,这个注解确实是AspectJ定义的,但Spring只是重用了这个注解,实现仍是Spring AOP本身的基于动态代理的实现。

sayHello被执行后的结果:

aop前置通知Before hello, aop aop后置通知After 返回通知AfterReturning

本段代码源自嘟嘟的博客,里面有具体的解释。

基于XML的配置

同理,再也不列出详细代码。

  • <aop:aspectj-autoproxy/> 启用AOP注解。
  • 如不采用注解,则用aop名称空间的标记描述切面。以下是一个等效的配置:
<bean id="logAspect" class="com.springdemo.aspect.LogAspect" />
<aop:config>
        <aop:aspect id="log"  ref="logAspect">
            <aop:pointcut id="logAop" expression="execution(* com.springdemo.service.impl.UserServiceImpl.sayHello(..)) and args(name)"/>
            <aop:before method="logBefore" pointcut-ref="logAop"/>
            <aop:after method="logAfter"  pointcut-ref="logAop"/>
            <aop:after-returning method="logAfterReturning"  pointcut-ref="logAop"/>
            <aop:after-throwing method="logAfterThrow" pointcut-ref="logAop"/>
            <!--<aop:around method="logAfterThrow"  pointcut-ref="logAop"/>-->
        </aop:aspect>
</aop:config>

  

Spring AOP原理 —— 动态代理

参考这篇知乎问答
首先,静态代理应该是广泛了解的概念了。它是OOP的一种设计模式,依赖于接口。
动态代理则是为了解决目标类方法愈来愈多时,代理类也要跟着膨胀的问题。由于动态代理类只要在invoke()方法中有选择地实现接口的方法。这对那些广泛适用的功能来讲特别适合,好比缓存、认证、log等等。

 
public class ProxyHandler implements InvocationHandler
{
    private Object tar;

    //绑定委托对象,并返回代理类
    public Object bind(Object tar)
    {
        this.tar = tar;
        //绑定该类实现的全部接口,取得代理类 
        return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
                                      tar.getClass().getInterfaces(),
                                      this);
    }

    // invoke中的逻辑能够对接口中的全部方法生效。
    public Object invoke(Object proxy , Method method , Object[] args)throws Throwable
    {
        Object result = null;
        //在调用具体函数方法前,执行功能处理
        result = method.invoke(tar,args);
        //在调用具体函数方法后,执行功能处理
        return result;
    }
}

  

至于InvocationHandler,它是JDK的java.lang.reflect名称空间里提供的一个类,用来实现动态代理。具体可见Java Dynamic Proxy API

能够看出,动态代理的功能十分强大,所以获得了普遍的应用。好比单元测试中的mock框架,MyBatis的sql注解等等。

最后,动态代理还是须要接口的。而借助另外一个第三方类库CGLib,则能够动态生成字节码,不依赖于接口。Spring AOP对这两种技术都有使用。参见CSDN

相关文章
相关标签/搜索